ASP.NET MVC: establezca IIdentity o IPrincipal personalizado
Necesito hacer algo bastante simple: en mi aplicación ASP.NET MVC, quiero configurar un IIdentity/IPrincipal personalizado. Lo que sea más fácil/adecuado. Quiero ampliar el valor predeterminado para poder llamar a algo como User.Identity.Id
y User.Identity.Role
. Nada especial, sólo algunas propiedades adicionales.
He leído toneladas de artículos y preguntas, pero siento que lo estoy haciendo más difícil de lo que realmente es. Pensé que iba a ser fácil. Si un usuario inicia sesión, quiero establecer una identidad II personalizada. Entonces pensé: lo implementaré Application_PostAuthenticateRequest
en mi global.asax. Sin embargo, eso se llama en cada solicitud, y no quiero hacer una llamada a la base de datos en cada solicitud que solicitaría todos los datos de la base de datos y colocaría un objeto IPrincipal personalizado. Eso también parece muy innecesario, lento y en el lugar equivocado (haciendo llamadas a la base de datos allí), pero podría estar equivocado. ¿O de dónde más vendrían esos datos?
Entonces pensé, cada vez que un usuario inicia sesión, puedo agregar algunas variables necesarias en mi sesión, que agrego a la IIdentidad personalizada en el Application_PostAuthenticateRequest
controlador de eventos. Sin embargo, el mío Context.Session
está null
ahí, así que ese tampoco es el camino a seguir.
Llevo un día trabajando en esto y siento que me falta algo. Esto no debería ser demasiado difícil de hacer, ¿verdad? También estoy un poco confundido por todas las cosas (semi)relacionadas que vienen con esto. MembershipProvider
, MembershipUser
, RoleProvider
, ProfileProvider
, IPrincipal
, IIdentity
, FormsAuthentication
.... ¿Soy el único al que le parece muy confuso todo esto?
Si alguien pudiera decirme una solución simple, elegante y eficiente para almacenar algunos datos adicionales en un IIdentity sin toda la confusión adicional... ¡sería genial! Sé que hay preguntas similares sobre SO, pero si la respuesta que necesito está ahí, debo haberla pasado por alto.
Así es como lo hago.
Decidí usar IPrincipal en lugar de IIdentity porque significa que no tengo que implementar tanto IIdentity como IPrincipal.
Crear la interfaz
interface ICustomPrincipal : IPrincipal { int Id { get; set; } string FirstName { get; set; } string LastName { get; set; } }
PersonalizadoPrincipal
public class CustomPrincipal : ICustomPrincipal { public IIdentity Identity { get; private set; } public bool IsInRole(string role) { return false; } public CustomPrincipal(string email) { this.Identity = new GenericIdentity(email); } public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
CustomPrincipalSerializeModel: para serializar información personalizada en el campo de datos de usuario en el objeto FormsAuthenticationTicket.
public class CustomPrincipalSerializeModel { public int Id { get; set; } public string FirstName { get; set; } public string LastName { get; set; } }
Método de inicio de sesión: configuración de una cookie con información personalizada
if (Membership.ValidateUser(viewModel.Email, viewModel.Password)) { var user = userRepository.Users.Where(u => u.Email == viewModel.Email).First(); CustomPrincipalSerializeModel serializeModel = new CustomPrincipalSerializeModel(); serializeModel.Id = user.Id; serializeModel.FirstName = user.FirstName; serializeModel.LastName = user.LastName; JavaScriptSerializer serializer = new JavaScriptSerializer(); string userData = serializer.Serialize(serializeModel); FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket( 1, viewModel.Email, DateTime.Now, DateTime.Now.AddMinutes(15), false, userData); string encTicket = FormsAuthentication.Encrypt(authTicket); HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket); Response.Cookies.Add(faCookie); return RedirectToAction("Index", "Home"); }
Global.asax.cs: lectura de cookies y sustitución del objeto HttpContext.User; esto se realiza anulando PostAuthenticateRequest
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e) { HttpCookie authCookie = Request.Cookies[FormsAuthentication.FormsCookieName]; if (authCookie != null) { FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value); JavaScriptSerializer serializer = new JavaScriptSerializer(); CustomPrincipalSerializeModel serializeModel = serializer.Deserialize<CustomPrincipalSerializeModel>(authTicket.UserData); CustomPrincipal newUser = new CustomPrincipal(authTicket.Name); newUser.Id = serializeModel.Id; newUser.FirstName = serializeModel.FirstName; newUser.LastName = serializeModel.LastName; HttpContext.Current.User = newUser; } }
Acceso en vistas de Razor
@((User as CustomPrincipal).Id) @((User as CustomPrincipal).FirstName) @((User as CustomPrincipal).LastName)
y en código:
(User as CustomPrincipal).Id
(User as CustomPrincipal).FirstName
(User as CustomPrincipal).LastName
Creo que el código se explica por sí mismo. Si no es así, házmelo saber.
Además, para facilitar aún más el acceso, puede crear un controlador base y anular el objeto Usuario devuelto (HttpContext.User):
public class BaseController : Controller
{
protected virtual new CustomPrincipal User
{
get { return HttpContext.User as CustomPrincipal; }
}
}
y luego, para cada controlador:
public class AccountController : BaseController
{
// ...
}
lo que le permitirá acceder a campos personalizados en un código como este:
User.Id
User.FirstName
User.LastName
Pero esto no funcionará en las vistas internas. Para eso, necesitaría crear una implementación WebViewPage personalizada:
public abstract class BaseViewPage : WebViewPage
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
public abstract class BaseViewPage<TModel> : WebViewPage<TModel>
{
public virtual new CustomPrincipal User
{
get { return base.User as CustomPrincipal; }
}
}
Conviértalo en un tipo de página predeterminado en Views/web.config:
<pages pageBaseType="Your.Namespace.BaseViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
</namespaces>
</pages>
y en vistas, puedes acceder así:
@User.FirstName
@User.LastName
No puedo hablar directamente de ASP.NET MVC, pero para ASP.NET Web Forms, el truco consiste en crear una cookie FormsAuthenticationTicket
y cifrarla en una cookie una vez que el usuario se ha autenticado. De esta manera, solo tiene que llamar a la base de datos una vez (o a AD o lo que esté usando para realizar su autenticación), y cada solicitud posterior se autenticará según el ticket almacenado en la cookie.
Un buen artículo sobre esto: http://www.ondotnet.com/pub/a/dotnet/2004/02/02/ Effectiveformsauth.html (enlace roto)
Editar:
Dado que el enlace anterior está roto, recomendaría la solución de LukeP en su respuesta anterior: https://stackoverflow.com/a/10524305 ; también sugeriría que la respuesta aceptada se cambie por esa.
Edición 2: una alternativa para el enlace roto: https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/ Effectiveformsauth.html
A continuación se muestra un ejemplo para realizar el trabajo. bool isValid se establece mirando algún almacén de datos (digamos su base de datos de usuario). UserID es solo una identificación que mantengo. Puede agregar información adicional como la dirección de correo electrónico a los datos del usuario.
protected void btnLogin_Click(object sender, EventArgs e)
{
//Hard Coded for the moment
bool isValid=true;
if (isValid)
{
string userData = String.Empty;
userData = userData + "UserID=" + userID;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(30), true, userData);
string encTicket = FormsAuthentication.Encrypt(ticket);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
Response.Cookies.Add(faCookie);
//And send the user where they were heading
string redirectUrl = FormsAuthentication.GetRedirectUrl(username, false);
Response.Redirect(redirectUrl);
}
}
en golbal asax agregue el siguiente código para recuperar su información
protected void Application_AuthenticateRequest(Object sender, EventArgs e)
{
HttpCookie authCookie = Request.Cookies[
FormsAuthentication.FormsCookieName];
if(authCookie != null)
{
//Extract the forms authentication cookie
FormsAuthenticationTicket authTicket =
FormsAuthentication.Decrypt(authCookie.Value);
// Create an Identity object
//CustomIdentity implements System.Web.Security.IIdentity
CustomIdentity id = GetUserIdentity(authTicket.Name);
//CustomPrincipal implements System.Web.Security.IPrincipal
CustomPrincipal newUser = new CustomPrincipal();
Context.User = newUser;
}
}
Cuando vaya a utilizar la información más adelante, puede acceder a su principal personalizado de la siguiente manera.
(CustomPrincipal)this.User
or
(CustomPrincipal)this.Context.User
esto le permitirá acceder a información de usuario personalizada.
Aquí hay una solución si necesita conectar algunos métodos a @User para usarlos en sus vistas. No hay solución para ninguna personalización seria de la membresía, pero si la pregunta original fuera necesaria solo para las vistas, entonces quizás esto sería suficiente. Lo siguiente se usó para verificar una variable devuelta por un filtro de autorización, que se usa para verificar si algunos enlaces deben presentarse o no (no para ningún tipo de lógica de autorización o concesión de acceso).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Security.Principal;
namespace SomeSite.Web.Helpers
{
public static class UserHelpers
{
public static bool IsEditor(this IPrincipal user)
{
return null; //Do some stuff
}
}
}
Luego simplemente agregue una referencia en las áreas web.config y llámela como se muestra a continuación en la vista.
@User.IsEditor()
Basado en la respuesta de LukeP , y agregue algunos métodos para configurar timeout
y requireSSL
cooperar Web.config
.
Los enlaces de referencias.
- MSDN, explicado: autenticación de formularios en ASP.NET 2.0
- MSDN, clase de autenticación de formularios
- SO, valor de "tiempo de espera" de autenticación de formularios de acceso .net en el código
Códigos modificados de LukeP
1, conjunto timeout
basado en Web.Config
. FormsAuthentication.Timeout obtendrá el valor de tiempo de espera, que está definido en web.config . Envolví lo siguiente para que fuera una función, que devuelve un ticket
respaldo.
int version = 1;
DateTime now = DateTime.Now;
// respect to the `timeout` in Web.config.
TimeSpan timeout = FormsAuthentication.Timeout;
DateTime expire = now.Add(timeout);
bool isPersist = false;
FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(
version,
name,
now,
expire,
isPersist,
userData);
2. Configurar la cookie para que sea segura o no, según la RequireSSL
configuración.
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;