ASP.NET MVC-Set custom IIdentity or principal

je dois faire quelque chose assez simple: dans mon ASP.NET MVC application, je veux définir un IIdentity / IPrincipal personnalisé. Selon ce qui est le plus facile / plus approprié. Je veux étendre la valeur par défaut pour que je puisse appeler quelque chose comme User.Identity.Id et User.Identity.Role . Rien d'extravagant, juste quelques propriétés supplémentaires.

j'ai lu des tonnes d'articles et de questions, mais j'ai l'impression de rendre les choses plus difficiles qu'elles ne le sont en réalité. Je pensais que ce serait facile. Si un utilisateur se connecte, je veux mettre un personnalisé à l'Identité. Alors j'ai pensé, je vais implémenter Application_PostAuthenticateRequest dans mon global.asax. Cependant, cela est appelé sur chaque demande, et je ne veux pas faire un appel à la base de données sur chaque demande qui demanderait toutes les données de la base de données et mettre dans un objet principal personnalisé. Cela semble également très inutile, lent, et au mauvais endroit (faire des appels de base de données là-bas), mais je pourrais me tromper. Ou là où d'autre aurait que viennent les données?

donc j'ai pensé, chaque fois qu'un utilisateur se connecte IPrincipal , IIdentity , FormsAuthentication .... Suis-je le seul qui trouve tout cela très confus?

si quelqu'un pouvait me dire une solution simple, élégante, et efficace pour stocker quelques données supplémentaires sur une identité sans tout le fuzz supplémentaire.. ce serait formidable! Je sais qu'il y a des questions similaires, mais si la réponse est là-dedans, j'ai dû oublier.

609
demandé sur Kiquenet 2009-06-30 19:18:15

9 réponses

voilà comment je fais.

j'ai décidé d'utiliser IPrincipal au lieu de IIdentity parce que cela signifie que je n'ai pas à mettre en œuvre à la fois IIdentity et IPrincipal.

  1. créer l'interface

    interface ICustomPrincipal : IPrincipal
    {
        int Id { get; set; }
        string FirstName { get; set; }
        string LastName { get; set; }
    }
    
  2. CustomPrincipal

    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 - pour la sérialisation personnalisée de l'information dans userdata champ dans l'objet FormsAuthenticationTicket.

    public class CustomPrincipalSerializeModel
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
  4. LogIn méthode - mise en place d'un cookie avec des informations personnalisées

    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 - lecture du cookie et remplacement de HttpContext.Objet utilisateur, ceci est fait en écrasant 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. l'Accès à la Rasoir vues

    @((User as CustomPrincipal).Id)
    @((User as CustomPrincipal).FirstName)
    @((User as CustomPrincipal).LastName)
    

et dans le code:

    (User as CustomPrincipal).Id
    (User as CustomPrincipal).FirstName
    (User as CustomPrincipal).LastName

je pense que le code est explicite. Si ce n'est pas le cas, faites le moi savoir.

en plus de faciliter l'accès, vous pouvez créer un contrôleur de base et surcharger L'objet utilisateur retourné (HttpContext.Utilisateur):

public class BaseController : Controller
{
    protected virtual new CustomPrincipal User
    {
        get { return HttpContext.User as CustomPrincipal; }
    }
}

et ensuite, pour chaque contrôleur:

public class AccountController : BaseController
{
    // ...
}

qui vous permettra d'accéder aux champs personnalisés dans le code comme ceci:

User.Id
User.FirstName
User.LastName

mais cela ne fonctionnera pas vues à l'intérieur. Pour cela, vous devez créer une mise en œuvre de page webviewpage personnalisé:

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; }
    }
}

en faire un type de page par défaut dans 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>

et dans vues, vous pouvez y accéder comme ceci:

@User.FirstName
@User.LastName
799
répondu LukeP 2017-05-31 17:36:41

Je ne peux pas parler directement pour ASP.NET MVC, mais pour ASP.NET formulaires Web, le truc est de créer un FormsAuthenticationTicket et de le chiffrer dans un cookie une fois que l'Utilisateur a été authentifié. De cette façon, vous n'avez qu'à appeler la base de données qu'une seule fois (ou une seule annonce publicitaire ou tout ce que vous utilisez pour effectuer votre authentification), et chaque requête subséquente s'authentifiera sur la base du ticket stocké dans le cookie.

un bon article sur ceci: http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html (lien brisé)

Edit:

puisque le lien ci-dessus est cassé, je recommande la solution de LukeP dans sa réponse ci-dessus: https://stackoverflow.com/a/10524305 - je suggérerais également que la réponse acceptée soit remplacée par celle-ci.

Edit 2: Un alternative pour le lien brisé: https://web.archive.org/web/20120422011422/http://ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html

105
répondu John Rasch 2017-05-23 12:10:41

Voici un exemple pour faire le travail. bool isValid est défini en regardant quelques données de magasin (disons votre base de données d'utilisateur). UserID est juste une identification que je maintiens. Vous pouvez ajouter des informations supplémentaires comme l'adresse e-mail aux données de l'utilisateur.

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);
     }
}

dans le golbal asax ajouter le code suivant pour récupérer vos informations

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;
    }
}

lorsque vous allez utiliser l'information plus tard, vous pouvez accéder à votre principal personnalisé comme suit.

(CustomPrincipal)this.User
or 
(CustomPrincipal)this.Context.User

cela vous permettra d'accéder aux informations personnalisées de l'utilisateur.

63
répondu Sriwantha Attanayake 2014-10-06 19:35:43

MVC vous fournit la méthode Onauthorizate qui tient de vos classes de contrôleur. Ou, vous pouvez utiliser un filtre d'action personnalisé pour effectuer l'autorisation. MVC le rend assez facile à faire. J'ai posté un billet de blog à ce sujet ici. http://www.bradygaster.com/post/custom-authentication-with-mvc-3.0

15
répondu brady gaster 2014-05-07 23:37:54

Voici une solution si vous avez besoin de raccorder certaines méthodes à @User pour une utilisation dans vos vues. Il n'y a pas de solution pour toute personnalisation sérieuse de l'adhésion, mais si la question initiale était nécessaire pour les vues uniquement, alors cela serait peut-être suffisant. Ce qui suit a été utilisé pour vérifier une variable retournée par un authorizefilter, utilisé pour vérifier si certains liens wehere to be presented ou non(pas pour tout type de logique d'autorisation ou d'octroi d'accès).

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
            }
        }
    }

alors il suffit d'ajouter une référence dans les domaines du web.config, et de l'appeler comme ci-dessous dans la vue.

@User.IsEditor()
9
répondu Baseless 2013-03-09 23:10:04

basé sur la réponse de LukeP , et ajouter quelques méthodes pour configurer timeout et requireSSL a coopéré avec Web.config .

Les références des liens

Codes modifiés de LukeP

1, Réglez timeout basé sur Web.Config . Le FormsAuthentication.Timeout obtiendra la valeur timeout, qui est définie dans web.config. J'ai enveloppé les suivants pour être une fonction qui retourne un ticket en arrière.

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, Configurer le cookie pour qu'il soit sécurisé ou non, sur la base de la configuration RequireSSL .

HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encTicket);
// respect to `RequreSSL` in `Web.Config`
bool bSSL = FormsAuthentication.RequireSSL;
faCookie.Secure = bSSL;
3
répondu AechoLiu 2017-05-23 12:18:25

D'accord, donc je suis un cryptkeeper sérieux ici en soulevant cette très vieille question, mais il y a une approche beaucoup plus simple à cela, qui a été abordée par @Baserz ci-dessus. Et c'est d'utiliser une combinaison de méthodes D'Extension C# et de cache (N'utilisez pas session).

en fait, Microsoft a déjà fourni un certain nombre de ces extensions dans l'espace de noms Microsoft.AspNet.Identity.IdentityExtensions . Par exemple, GetUserId() est une méthode d'extension qui retourne Id d'utilisateur. Il y a aussi GetUserName() et FindFirstValue() , qui renvoie des revendications fondées sur le principe.

donc vous n'avez besoin que d'inclure l'espace de noms, puis d'appeler User.Identity.GetUserName() pour obtenir le nom d'utilisateur tel que configuré par ASP.NET identité.

ASP.NET L'identité n'est pas de source ouverte, et je n'ai pas pris la peine de la rétro-ingénierie. Cependant, si c'est non, alors vous pouvez écrire votre propre méthode d'extension, qui sera cache ce résultat pour une période de temps spécifique.

3
répondu Erik Funkenbusch 2017-05-31 20:49:11

comme ajout au Code LukeP pour les utilisateurs de formulaires Web (pas MVC) si vous voulez simplifier l'accès dans le code derrière vos pages, il suffit d'ajouter le code ci-dessous à une page de base et dériver la page de base dans toutes vos pages:

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

ainsi dans votre code derrière vous pouvez simplement accéder:

User.FirstName or User.LastName

ce que je manque dans un scénario de formulaire Web, c'est comment obtenir le même comportement en code non lié à la page, par exemple dans httpmodules dois-je toujours ajouter un plâtre dans chaque classe ou y a-t-il un moyen plus intelligent d'obtenir cela?

Merci pour vos réponses et merci à LukeP depuis que j'ai utilisé vos exemples comme une base pour mon utilisateur personnalisée (qui a maintenant User.Roles , User.Tasks , User.HasPath(int) , User.Settings.Timeout et beaucoup d'autres belles choses)

2
répondu Manight 2013-07-25 13:56:15

j'ai essayé la solution suggérée par LukeP et j'ai trouvé qu'elle ne supporte pas L'attribut Authorize. Donc, je l'ai modifiée un peu.

public class UserExBusinessInfo
{
    public int BusinessID { get; set; }
    public string Name { get; set; }
}

public class UserExInfo
{
    public IEnumerable<UserExBusinessInfo> BusinessInfo { get; set; }
    public int? CurrentBusinessID { get; set; }
}

public class PrincipalEx : ClaimsPrincipal
{
    private readonly UserExInfo userExInfo;
    public UserExInfo UserExInfo => userExInfo;

    public PrincipalEx(IPrincipal baseModel, UserExInfo userExInfo)
        : base(baseModel)
    {
        this.userExInfo = userExInfo;
    }
}

public class PrincipalExSerializeModel
{
    public UserExInfo UserExInfo { get; set; }
}

public static class IPrincipalHelpers
{
    public static UserExInfo ExInfo(this IPrincipal @this) => (@this as PrincipalEx)?.UserExInfo;
}


    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginModel details, string returnUrl)
    {
        if (ModelState.IsValid)
        {
            AppUser user = await UserManager.FindAsync(details.Name, details.Password);

            if (user == null)
            {
                ModelState.AddModelError("", "Invalid name or password.");
            }
            else
            {
                ClaimsIdentity ident = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
                AuthManager.SignOut();
                AuthManager.SignIn(new AuthenticationProperties { IsPersistent = false }, ident);

                user.LastLoginDate = DateTime.UtcNow;
                await UserManager.UpdateAsync(user);

                PrincipalExSerializeModel serializeModel = new PrincipalExSerializeModel();
                serializeModel.UserExInfo = new UserExInfo()
                {
                    BusinessInfo = await
                        db.Businesses
                        .Where(b => user.Id.Equals(b.AspNetUserID))
                        .Select(b => new UserExBusinessInfo { BusinessID = b.BusinessID, Name = b.Name })
                        .ToListAsync()
                };

                JavaScriptSerializer serializer = new JavaScriptSerializer();

                string userData = serializer.Serialize(serializeModel);

                FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
                         1,
                         details.Name,
                         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 RedirectToLocal(returnUrl);
            }
        }
        return View(details);
    }

et enfin dans Global.asax.cs

    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();
            PrincipalExSerializeModel serializeModel = serializer.Deserialize<PrincipalExSerializeModel>(authTicket.UserData);
            PrincipalEx newUser = new PrincipalEx(HttpContext.Current.User, serializeModel.UserExInfo);
            HttpContext.Current.User = newUser;
        }
    }

maintenant je peux accéder aux données dans les vues et les contrôleurs simplement en appelant

User.ExInfo()

pour me déconnecter je viens d'appeler

AuthManager.SignOut();

où AuthManager est

HttpContext.GetOwinContext().Authentication
0
répondu Vasily Ivanov 2018-06-26 09:02:53