ASP.NET MVC-Establecer identidad personalizada o IPrincipal




asp.net-mvc forms-authentication (6)

Necesito hacer algo bastante simple: en mi aplicación ASP.NET MVC, quiero establecer un IIdentity / IPrincipal personalizado. Lo que sea más fácil / más adecuado. Quiero extender el valor predeterminado para poder llamar a algo como User.Identity.Id y User.Identity.Role . Nada de lujos, solo algunas propiedades extras.

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 personalizada. Así que pensé, implementaré Application_PostAuthenticateRequest en mi global.asax. Sin embargo, esto se realiza en todas las solicitudes, y no quiero hacer una llamada a la base de datos en todas las solicitudes que solicitarían todos los datos de la base de datos y colocarían un objeto IPrincipal personalizado. Eso también parece muy innecesario, lento y en el lugar equivocado (hacer llamadas a la base de datos allí) pero podría estar equivocado. ¿O de dónde más vendrían esos datos?

Así que pensé, cada vez que un usuario inicia sesión, puedo agregar algunas variables necesarias en mi sesión, que agrego a la identidad personalizada en el controlador de eventos Application_PostAuthenticateRequest . Sin embargo, mi Context.Session es null allí, por lo que tampoco es el camino a seguir.

He estado trabajando en esto por un día y siento que me estoy perdiendo 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 , RoleProvider , IPrincipal , IIdentity , FormsAuthentication ... ¿Soy el único que encuentra todo esto muy confuso?

Si alguien pudiera decirme una solución simple, elegante y eficiente para almacenar algunos datos adicionales en una identidad digital sin toda la confusión adicional ... ¡eso sería genial! Sé que hay preguntas similares en SO, pero si la respuesta que necesito está ahí, debo haberla pasado por alto.


Aquí hay un ejemplo para hacer el trabajo. bool isValid se establece al observar algunos almacenes de datos (digamos, su base de datos de usuarios). UserID es solo una identificación que estoy manteniendo. 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 el 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 usar en sus vistas. No hay solución para la personalización seria de la membresía, pero si la pregunta original fuera necesaria solo para las vistas, tal vez esto sea suficiente. Lo siguiente se usó para verificar una variable devuelta por un authorizefilter, usado para verificar si algunos enlaces se presentarán 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 abajo en la vista.

@User.IsEditor()

Basado en la respuesta de LukeP , y agregue algunos métodos para configurar el timeout y requireSSL cooperó con Web.config .

Los enlaces de referencias

Códigos modificados de LukeP

1, Establecer el timeout basado en Web.Config . El FormsAuthentication.Timeout obtendrá el valor de tiempo de espera, que se define en web.config. Envolví lo siguiente para ser una función, que devuelve un ticket .

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, configure la cookie para que sea segura o no, según la configuración de RequireSSL .

HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;

Como una adición al código LukeP para usuarios de Web Forms (no MVC) si desea simplificar el acceso en el código que se encuentra detrás de sus páginas, simplemente agregue el código a continuación a una página base y derive la página base en todas sus páginas:

Public Overridable Shadows ReadOnly Property User() As CustomPrincipal
    Get
        Return DirectCast(MyBase.User, CustomPrincipal)
    End Get
End Property

Así que en su código puede simplemente acceder a:

User.FirstName or User.LastName

Lo que me falta en un escenario de Web Form es cómo obtener el mismo comportamiento en un código no vinculado a la página. Por ejemplo, en httpmodules, ¿ debería agregar siempre un reparto en cada clase o hay una forma más inteligente de obtener esto?

Gracias por sus respuestas y gracias a LukeP ya que usé sus ejemplos como base para mi usuario personalizado (que ahora tiene User.Roles , User.Tasks , User.HasPath(int) , User.Settings.Timeout y muchas otras cosas agradables)


MVC le proporciona el método OnAuthorize que se cuelga de sus clases de controlador. O bien, puede utilizar un filtro de acción personalizado para realizar la autorización. MVC lo hace bastante fácil de hacer. He publicado una entrada de blog sobre esto aquí. http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0


No puedo hablar directamente para ASP.NET MVC, pero para los formularios web de ASP.NET, el truco consiste en crear un FormsAuthenticationTicket y cifrarlo en una cookie una vez que el usuario haya sido autenticado. De esta manera, solo tiene que llamar a la base de datos una vez (o AD o lo que esté usando para realizar su autenticación), y cada solicitud subsiguiente 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: share - También sugeriría que la respuesta aceptada se cambie a 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





iidentity