it-swarm.com.de

ASP.NET MVC - Festlegen der benutzerdefinierten IIdentity oder IPrincipal

Ich muss etwas ziemlich Einfaches tun: In meiner ASP.NET MVC-Anwendung möchte ich eine benutzerdefinierte IIdentity/IPrincipal festlegen. Was auch immer einfacher/geeigneter ist. Ich möchte den Standard so erweitern, dass ich so etwas wie User.Identity.Id und User.Identity.Role aufrufen kann. Nichts Besonderes, nur ein paar zusätzliche Eigenschaften.

Ich habe Unmengen von Artikeln und Fragen gelesen, aber ich habe das Gefühl, dass ich es schwieriger mache, als es tatsächlich ist. Ich dachte es wäre einfach. Wenn sich ein Benutzer anmeldet, möchte ich eine benutzerdefinierte Identität festlegen. Also dachte ich, ich werde Application_PostAuthenticateRequest in meine global.asax implementieren. Dies wird jedoch bei jeder Anforderung aufgerufen, und ich möchte nicht bei jeder Anforderung, bei der alle Daten aus der Datenbank angefordert und in ein benutzerdefiniertes IPrincipal-Objekt eingefügt werden, die Datenbank aufrufen. Das scheint auch sehr unnötig, langsam und am falschen Ort (Datenbankaufrufe dort), aber ich könnte mich irren. Oder woher kommen diese Daten sonst noch?

Also dachte ich, wenn sich ein Benutzer anmeldet, kann ich in meiner Sitzung einige notwendige Variablen hinzufügen, die ich in der Ereignisbehandlungsroutine Application_PostAuthenticateRequest zur benutzerdefinierten Identität hinzufüge. Allerdings ist mein Context.Session dort null, so dass das auch nicht der richtige Weg ist.

Ich arbeite jetzt seit einem Tag daran und habe das Gefühl, etwas zu verpassen. Das sollte nicht zu schwer sein, oder? Ich bin auch ein bisschen verwirrt von all den (halb) verwandten Dingen, die damit einhergehen. MembershipProvider, MembershipUser, RoleProvider, ProfileProvider, IPrincipal, IIdentity, FormsAuthentication.... Bin ich der einzige, der das alles sehr verwirrend findet?

Wenn mir jemand eine einfache, elegante und effiziente Lösung nennen könnte, um einige zusätzliche Daten auf einer IIdentity zu speichern, ohne all die zusätzlichen Unschärfen. Das wäre großartig! Ich weiß, dass es ähnliche Fragen zu SO gibt, aber wenn die Antwort, die ich brauche, dort drin ist, muss ich sie übersehen haben.

634
Razzie

So mache ich das.

Ich habe mich für die Verwendung von IPrincipal anstelle von IIdentity entschieden, da ich nicht sowohl IIdentity als auch IPrincipal implementieren muss.

  1. Erstellen Sie die Schnittstelle

    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 - zum Serialisieren benutzerdefinierter Informationen in ein Benutzerdatenfeld im FormsAuthenticationTicket-Objekt.

    public class CustomPrincipalSerializeModel
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
  4. Anmeldemethode - Einrichten eines Cookies mit benutzerdefinierten Informationen

    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 - Das Lesen von Cookies und das Ersetzen von HttpContext.User-Objekten erfolgt durch Überschreiben von 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. Zugriff in Razor-Ansichten

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

und im Code:

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

Ich denke, der Code ist selbsterklärend. Wenn nicht, lass es mich wissen.

Um den Zugriff zu vereinfachen, können Sie außerdem einen Basiscontroller erstellen und das zurückgegebene Benutzerobjekt (HttpContext.User) überschreiben:

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

und dann für jeden Controller:

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

so können Sie auf benutzerdefinierte Felder in folgendem Code zugreifen:

User.Id
User.FirstName
User.LastName

Dies funktioniert jedoch nicht in Ansichten. Dazu müssten Sie eine benutzerdefinierte WebViewPage-Implementierung erstellen:

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

Machen Sie es zu einem Standardseitentyp in 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>

in Ansichten können Sie folgendermaßen darauf zugreifen:

@User.FirstName
@User.LastName
823
LukeP

Ich kann nicht direkt für ASP.NET MVC sprechen, aber für ASP.NET Web Forms besteht der Trick darin, ein FormsAuthenticationTicket zu erstellen und es nach der Authentifizierung des Benutzers in ein Cookie zu verschlüsseln. Auf diese Weise müssen Sie die Datenbank nur einmal aufrufen (oder AD oder was auch immer Sie verwenden, um Ihre Authentifizierung durchzuführen), und jede nachfolgende Anforderung wird basierend auf dem im Cookie gespeicherten Ticket authentifiziert.

Ein guter Artikel dazu: http://www.ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html (defekter Link)

Bearbeiten:

Da der obige Link nicht funktioniert, würde ich die Lösung von LukeP in seiner obigen Antwort empfehlen: https://stackoverflow.com/a/10524305 - Ich würde auch vorschlagen, die akzeptierte Antwort in diese Antwort zu ändern.

Edit 2: Eine Alternative für den defekten Link: https://web.archive.org/web/20120422011422/http:// ondotnet.com/pub/a/dotnet/2004/02/02/effectiveformsauth.html

107
John Rasch

Hier ist ein Beispiel, um die Arbeit zu erledigen. bool isValid wird durch einen Blick auf einen Datenspeicher festgelegt (sagen wir, Ihre Benutzerdatenbank). UserID ist nur eine ID, die ich pflege. Sie können den Benutzerdaten zusätzliche Informationen wie eine E-Mail-Adresse hinzufügen.

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

fügen Sie im Golbal Asax den folgenden Code hinzu, um Ihre Informationen abzurufen

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

Wenn Sie die Informationen später verwenden möchten, können Sie wie folgt auf Ihr benutzerdefiniertes Prinzipal zugreifen.

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

auf diese Weise können Sie auf benutzerdefinierte Benutzerinformationen zugreifen.

63

MVC stellt Ihnen die OnAuthorize-Methode zur Verfügung, die von Ihren Controller-Klassen abhängt. Sie können auch einen benutzerdefinierten Aktionsfilter verwenden, um die Autorisierung durchzuführen. MVC macht das ziemlich einfach. Ich habe hier einen Blog-Beitrag dazu gepostet. http://www.bradygaster.com/post/custom-authentication-with-mvc-3.

15
brady gaster

Hier ist eine Lösung, wenn Sie einige Methoden an @User anschließen müssen, um sie in Ihren Ansichten zu verwenden. Keine Lösung für ernsthafte Anpassungen der Mitgliedschaft, aber wenn die ursprüngliche Frage nur für Ansichten benötigt würde, würde dies vielleicht ausreichen. Das Folgende wurde verwendet, um eine Variable zu überprüfen, die von einem Berechtigungsfilter zurückgegeben wurde, um zu überprüfen, ob einige Links angezeigt wurden oder nicht (nicht für jede Art von Berechtigungslogik oder Zugriffsgewährung).

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

Fügen Sie dann einfach einen Verweis in die Bereiche web.config ein und rufen Sie ihn wie unten in der Ansicht auf.

@User.IsEditor()
10
Base

Basierend auf LukePs Antwort , und fügen Sie einige Methoden hinzu, um timeout und requireSSL einzurichten, die mit Web.config zusammengearbeitet haben.

Die Verweise Links

Geänderte Codes von LukeP

1, timeout basierend auf Web.Config einstellen. Das FormsAuthentication.Timeout erhält den Timeout-Wert, der in web.config definiert ist. Ich habe das Folgende als Funktion verpackt, die ein ticket zurückgibt.

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, Konfigurieren Sie das Cookie als sicher oder nicht sicher, basierend auf der RequireSSL Konfiguration.

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

Also gut, ich bin hier ein ernsthafter Kryptowärter, indem ich diese sehr alte Frage hochziehe, aber es gibt einen viel einfacheren Ansatz, der von @Baserz oben angesprochen wurde. Und das ist eine Kombination aus C # -Erweiterungsmethoden und Caching (KEINE Sitzung verwenden).

Tatsächlich hat Microsoft bereits eine Reihe solcher Erweiterungen im Namespace Microsoft.AspNet.Identity.IdentityExtensions bereitgestellt. Beispielsweise ist GetUserId() eine Erweiterungsmethode, die die Benutzer-ID zurückgibt. Es gibt auch GetUserName() und FindFirstValue(), die Ansprüche basierend auf dem IPrincipal zurückgeben.

Sie müssen also nur den Namespace einschließen und dann User.Identity.GetUserName() aufrufen, um den von ASP.NET Identity konfigurierten Benutzernamen abzurufen.

Ich bin nicht sicher, ob dies zwischengespeichert ist, da die ältere ASP.NET-Identität nicht aus Open-Source-Quellen stammt und ich mich nicht darum gekümmert habe, sie zurückzuentwickeln. Wenn dies jedoch nicht der Fall ist, können Sie Ihre eigene Erweiterungsmethode schreiben, die dieses Ergebnis für eine bestimmte Zeitspanne zwischenspeichert.

3

Als Ergänzung zu LukeP-Code für Web Forms-Benutzer (nicht MVC), wenn Sie den Zugriff auf den Code hinter Ihren Seiten vereinfachen möchten, fügen Sie einfach den folgenden Code zu einer Basisseite hinzu und leiten Sie die Basisseite auf allen Ihren Seiten ab:

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

So können Sie in Ihrem Code dahinter einfach zugreifen:

User.FirstName or User.LastName

Was mir in einem Web Form-Szenario fehlt, ist, wie ich dasselbe Verhalten in Code erzielen kann, der nicht an die Seite gebunden ist, zum Beispiel in httpmodules, sollte ich in jeder Klasse immer eine Besetzung hinzufügen oder gibt es eine klügerer Weg, um dies zu erhalten?

Danke für Ihre Antworten und danke an LukeP, da ich Ihre Beispiele als Basis für meinen benutzerdefinierten Benutzer verwendet habe (der jetzt User.Roles, User.Tasks, User.HasPath(int), User.Settings.Timeout und viele andere hat) Schöne Dinge)

2
Manight

Ich habe die von LukeP vorgeschlagene Lösung ausprobiert und festgestellt, dass sie das Authorize-Attribut nicht unterstützt. Also habe ich es ein bisschen modifiziert.

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

Und schließlich in 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;
        }
    }

Jetzt kann ich in Views und Controllern einfach per Anruf auf die Daten zugreifen

User.ExInfo()

Zum ausloggen rufe ich einfach an

AuthManager.SignOut();

wo AuthManager ist

HttpContext.GetOwinContext().Authentication
0
Vasily Ivanov