ASP.NET MVC: establezca IIdentity o IPrincipal personalizado

Resuelto Razzie asked hace 15 años • 9 respuestas

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.Idy 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_PostAuthenticateRequesten 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_PostAuthenticateRequestcontrolador de eventos. Sin embargo, el mío Context.Sessionestá nullahí, 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.

Razzie avatar Jun 30 '09 22:06 Razzie
Aceptado

Así es como lo hago.

Decidí usar IPrincipal en lugar de IIdentity porque significa que no tengo que implementar tanto IIdentity como IPrincipal.

  1. Crear la interfaz

    interface ICustomPrincipal : IPrincipal
    {
        int Id { get; set; }
        string FirstName { get; set; }
        string LastName { get; set; }
    }
    
  2. 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; }
    }
    
  3. 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; }
    }
    
  4. 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");
    }
    
  5. 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;
        }
    }
    
  6. 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
LukeP avatar May 09 '2012 21:05 LukeP

No puedo hablar directamente de ASP.NET MVC, pero para ASP.NET Web Forms, el truco consiste en crear una cookie FormsAuthenticationTickety 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

John Rasch avatar Jun 30 '2009 15:06 John Rasch

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.

Sriwantha Attanayake avatar Nov 20 '2009 10:11 Sriwantha Attanayake

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()
Base avatar Mar 09 '2013 23:03 Base

Basado en la respuesta de LukeP , y agregue algunos métodos para configurar timeouty requireSSLcooperar 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 timeoutbasado 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 ticketrespaldo.

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 RequireSSLconfiguración.

HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;
AechoLiu avatar Apr 23 '2013 09:04 AechoLiu