c# - custom - authorize mvc example
Identidad personalizada usando MVC5 y OWIN (4)
Apuesto a que HttpContext.Current.User es nulo. Así que en lugar de esto:
if (HttpContext.Current.User.Identity.IsAuthenticated)
puedes probar esto
if (HttpContext.Current.Request.IsAuthenticated)
Intento agregar propiedades personalizadas al ApplicationUser para un sitio web mediante MVC5 y autenticación OWIN. He leído https://stackoverflow.com/a/10524305/264607 y me gusta cómo se integra con el controlador base para acceder fácilmente a las nuevas propiedades. Mi problema es que cuando configuro la propiedad HTTPContext.Current.User en mi nuevo IPrincipal obtengo un error de referencia nulo:
[NullReferenceException: Object reference not set to an instance of an object.]
System.Web.Security.UrlAuthorizationModule.OnEnter(Object source, EventArgs eventArgs) +127
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +136
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +69
Aquí está mi código:
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
PatientPortalPrincipal newUser = new PatientPortalPrincipal();
newUser.BirthDate = user.BirthDate;
newUser.InvitationCode = user.InvitationCode;
newUser.PatientNumber = user.PatientNumber;
//Claim cPatient = new Claim(typeof(PatientPortalPrincipal).ToString(), );
HttpContext.Current.User = newUser;
}
}
public class PatientPortalPrincipal : ClaimsPrincipal, IPatientPortalPrincipal
{
public PatientPortalPrincipal(ApplicationUser user)
{
Identity = new GenericIdentity(user.UserName);
BirthDate = user.BirthDate;
InvitationCode = user.InvitationCode;
}
public PatientPortalPrincipal() { }
public new bool IsInRole(string role)
{
if(!string.IsNullOrWhiteSpace(role))
return Role.ToString().Equals(role);
return false;
}
public new IIdentity Identity { get; private set; }
public WindowsBuiltInRole Role { get; set; }
public DateTime BirthDate { get; set; }
public string InvitationCode { get; set; }
public string PatientNumber { get; set; }
}
public interface IPatientPortalPrincipal : IPrincipal
{
WindowsBuiltInRole Role { get; set; }
DateTime BirthDate { get; set; }
string InvitationCode { get; set; }
string PatientNumber { get; set; }
}
No he encontrado mucha documentación sobre cómo hacer esto, he leído estos artículos:
Los comentarios en el segundo enlace me indicaron que quizás utilice reclamos ( http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp ), pero el artículo está vinculado to no muestra cómo agregarlos a un IPrincipal
(que es lo que es HttpContext.Current.User
), o dónde se supone que debe agregarlos a una ClaimsIdentity
(que es la clase concreta que es el User
). Me estoy inclinando hacia el uso de reclamaciones, pero necesito saber dónde agregar estas nuevas reclamaciones al usuario.
Incluso si las reclamaciones son el camino a seguir, tengo curiosidad por saber qué estoy haciendo mal con mi IPrincipal personalizado, ya que parece que he implementado todo lo que requiere.
Está recibiendo una excepción porque HttpContext.Current.User.Identity.IsAuthenticated
devuelve false en el punto de verificación (también lo hace HttpContext.Current.Request.IsAuthenticated
).
Si elimina la instrucción if (HttpContext.Current.User.Identity.IsAuthenticated)
funcionará bien (al menos esta parte del código).
He intentado algo tan simple como esto:
BaseController.cs
public abstract class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
CustomPrincipal.cs
public class CustomPrincipal : IPrincipal
{
public IIdentity Identity { get; private set; }
public bool IsInRole(string role) { return false; }
public CustomPrincipal(string username)
{
this.Identity = new GenericIdentity(username);
}
public DateTime BirthDate { get; set; }
public string InvitationCode { get; set; }
public int PatientNumber { get; set; }
}
Global.asax.cs
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
CustomPrincipal customUser = new CustomPrincipal(User.Identity.Name);
customUser.BirthDate = DateTime.Now;
customUser.InvitationCode = "1234567890A";
customUser.PatientNumber = 100;
HttpContext.Current.User = customUser;
}
HomeController.cs
public ActionResult Index()
{
ViewBag.BirthDate = User.BirthDate;
ViewBag.InvitationCode = User.InvitationCode;
ViewBag.PatientNumber = User.PatientNumber;
return View();
}
Y esto está funcionando bien. Así que a menos que este código:
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
no está devolviendo un objeto de usuario válido (personalizado), el problema es con la instrucción if()
.
Su actualización se ve bien, y si está contento de almacenar datos como reclamos en una cookie, puede ir con ella, aunque personalmente odio el bloque de captura try {}
allí.
Lo que hago en cambio es esto:
BaseController.cs
[AuthorizeEx]
public abstract partial class BaseController : Controller
{
public IOwinContext OwinContext
{
get { return HttpContext.GetOwinContext(); }
}
public new ClaimsPrincipal User
{
get { return base.User as ClaimsPrincipal; }
}
public WorkContext WorkContext { get; set; }
}
Decoré la clase de controlador base con un atributo personalizado.
AuthorizeExAttribute.cs:
public class AuthorizeExAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
Ensure.Argument.NotNull(filterContext);
base.OnAuthorization(filterContext);
IPrincipal user = filterContext.HttpContext.User;
if (user.Identity.IsAuthenticated)
{
var ctrl = filterContext.Controller as BaseController;
ctrl.WorkContext = new WorkContext(user.Identity.Name);
}
}
}
Y WorkContext.cs:
public class WorkContext
{
private string _email;
private Lazy<User> currentUser;
private IAuthenticationService authService;
private ICacheManager cacheManager;
public User CurrentUser
{
get
{
var cachedUser = cacheManager.Get<User>(Constants.CacheUserKeyPrefix + this._email);
if (cachedUser != null)
{
return cachedUser;
}
else
{
var user = currentUser.Value;
cacheManager.Set(Constants.CacheUserKeyPrefix + this._email, user, 30);
return user;
}
}
}
public WorkContext(string email)
{
Ensure.Argument.NotNullOrEmpty(email);
this._email = email;
this.authService = DependencyResolver.Current.GetService<IAuthenticationService>();
this.cacheManager = DependencyResolver.Current.GetService<ICacheManager>();
this.currentUser = new Lazy<User>(() => authService.GetUserByEmail(email));
}
Entonces accedo al WorkContext de esta manera:
public class DashboardController : BaseController
{
public ActionResult Index()
{
ViewBag.User = WorkContext.CurrentUser;
return View();
}
}
Estoy usando la resolución de dependencia de authService
para resolver authService
y cacheManager
pero puedes omitir el almacenamiento en caché y reemplazar authService con ASP.NET Identity UserManager
, creo.
También quería dar crédito a lo que se debe, ya que la clase WorkContext está muy inspirada en el proyecto NugetGallery.
He tenido el mismo error.
Mi problema fue que con los usuarios anónimos no estaba configurando la identidad en IPrincipal. Hice esto solo cuando los usuarios iniciaron sesión con nombre de usuario. De lo contrario, la identidad era nula.
Mi solución fue establecer siempre la identidad. Si el usuario no está autenticado (usuario anónimo), IIdentity.IsAuthenticated se establece en falso. De lo contrario, es cierto.
Mi código:
private PrincipalCustom SetPrincipalIPAndBrowser()
{
return new PrincipalCustom
{
IP = RequestHelper.GetIPFromCurrentRequest(HttpContext.Current.Request),
Browser = RequestHelper.GetBrowserFromCurrentRequest(HttpContext.Current.Request),
/* User is not authenticated, but Identity must be set anyway. If not, error occurs */
Identity = new IdentityCustom { IsAuthenticated = false }
};
}
Puedo hacer que algo funcione con la seguridad basada en Claims
, por lo que, si está buscando hacer algo rápidamente, esto es lo que tengo en este momento:
En el proceso de inicio de sesión en AccountController
(el mío está dentro del método SignInAsync
), agregue un nuevo reclamo a la identidad creada por UserManager
:
private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
identity.AddClaim(new Claim("PatientNumber", user.PatientNumber)); //This is what I added
AuthenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity);
}
Luego en mis clases de controlador base simplemente agregué una propiedad:
private string _patientNumber;
public string PatientNumber
{
get
{
if (string.IsNullOrWhiteSpace(_patientNumber))
{
try
{
var cp = ClaimsPrincipal.Current.Identities.First();
var patientNumber = cp.Claims.First(c => c.Type == "PatientNumber").Value;
_patientNumber = patientNumber;
}
catch (Exception)
{
}
}
return _patientNumber;
}
}
Este enlace fue útil para el conocimiento de reclamaciones: http://msdn.microsoft.com/en-us/library/ms734687.aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1
Actualización para el problema con IPrincipal
Lo rastreé hasta la propiedad de Identity
. El problema era que estaba proporcionando un constructor predeterminado en la clase PatientPortalPrincipal
que no estaba configurando la propiedad Identidad. Lo que terminé haciendo fue eliminar el constructor predeterminado y llamar al constructor correcto desde Application_PostAuthenticateRequest
, el código actualizado está debajo
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext()));
ApplicationUser user = userManager.FindByName(HttpContext.Current.User.Identity.Name);
PatientPortalPrincipal newUser = new PatientPortalPrincipal(user);
newUser.BirthDate = user.BirthDate;
newUser.InvitationCode = user.InvitationCode;
newUser.PatientNumber = user.PatientNumber;
//Claim cPatient = new Claim(typeof(PatientPortalPrincipal).ToString(), );
HttpContext.Current.User = newUser;
}
}
¡Eso hace que todo funcione!