2015-01-07 5 views
5

Es gibt viele Proben Online OWIN/Katana mit Benutzern in einer Datenbank auf ausername/Passwort-Kombination und erzeugen einen Forderungsprinzipal, wie ...Anpassen der OWIN/Katana Usermanager Fabrik Verhalten

var userManager = context.OwinContext.GetUserManager<ApplicationUserManager>(); 
ApplicationUser user = await userManager.FindAsync(context.UserName, context.Password); 
// generate claims here... 

Basis zu finden Das ist in Ordnung, wenn Sie eine neue Anwendung erstellen und wollen, dass Entity Framework die Drecksarbeit erledigt. Aber ich habe eine acht Jahre alte monolithische Website, die gerade aktualisiert wurde, um eine anspruchsbasierte Authentifizierung zu verwenden. Unser Datenbanktreffer erfolgt manuell über DAL/SQL und von dort wird die ClaimsIdentity generiert.

Einige Leute meinen, dass OWIN einfacher zu benutzen ist als unser manueller Ansatz, aber ich hätte gern etwas von denen, die es benutzen.

Ist es möglich zu ändern, wie die UserManager-Fabrik Benutzer anhand ihrer Anmeldeinformationen findet? Oder gibt es einen anderen Ansatz, den ich verpasst habe? Alle Beispiele, die ich online finden kann, scheinen einen Ansatz zu verwenden, bei dem Entity Framework die Datenbank erstellt und die Suchvorgänge verwaltet.

+0

Ja, Sie können anpassen, wie die UserManager verhält sich. Die Art und Weise, wie es seine Daten erhält, ist durch die Implementierung des 'IUserStore'. Hier ist eine Antwort, die Ihnen bei der Anpassung helfen kann. http://stackoverflow.com/questions/19940014/asp-net-identity-with-ef-database-first-mvc5/21122865#21122865 – Shoe

Antwort

12

ASP.NET Identität ist ein wenig übermäßig komplex, würde ich sagen.
Im August 2014 haben sie die neue Version 2.1 angekündigt und die Dinge haben sich wieder geändert.
Zunächst einmal lassen Sie uns von EntityFramework loszuwerden:

public class User: IUser<int> 
{ 
    public User() 
    { 
     this.Roles = new List<string>(); 
     this.Claims = new List<UserClaim>(); 
    } 

    public User(string userName) 
     : this() 
    { 
     this.UserName = userName; 
    } 

    public User(int id, string userName): this() 
    { 
     this.Id = Id; 
     this.UserName = userName; 
    } 

    public int Id { get; set; } 
    public string UserName { get; set; } 
    public string PasswordHash { get; set; } 

    public bool LockoutEnabled { get; set; } 
    public DateTime? LockoutEndDateUtc { get; set; } 
    public bool TwoFactorEnabled { get; set; } 

    public IList<string> Roles { get; private set; } 
    public IList<UserClaim> Claims { get; private set; } 
} 

Wie können Sie:

Uninstall-Package Microsoft.AspNet.Identity.EntityFramework 

Jetzt haben wir unsere eigene Definition von User Implementierung der Schnittstelle IUser (Microsoft.AspNet.Identity) implementieren siehe Ich habe den Typ meines Id (int) definiert.

Dann müssen Sie Ihre benutzerdefinierte UserManager erben von Microsoft.AspNet.Identity.UserManager unter Angabe Ihrer Benutzer-Typ und der Schlüssel-Typ definieren.

public class UserManager : UserManager<User, int> 
{ 
    public UserManager(IUserStore<User, int> store): base(store) 
    { 
     this.UserLockoutEnabledByDefault = false; 
     // this.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(10); 
     // this.MaxFailedAccessAttemptsBeforeLockout = 10; 
     this.UserValidator = new UserValidator<User, int>(this) 
     { 
      AllowOnlyAlphanumericUserNames = false, 
      RequireUniqueEmail = false 
     }; 

     // Configure validation logic for passwords 
     this.PasswordValidator = new PasswordValidator 
     { 
      RequiredLength = 4, 
      RequireNonLetterOrDigit = false, 
      RequireDigit = false, 
      RequireLowercase = false, 
      RequireUppercase = false, 
     }; 
    } 
} 

Ich habe meine Validierungsregeln hier implementiert, aber Sie können es draußen behalten, wenn Sie bevorzugen.

UserManager benötigt einen UserStore (IUserStore).

Hier definieren Sie Ihre DB-Logik. Es gibt einige zu implementierende Schnittstellen. Nicht alle sind jedoch obligatorisch.

public class UserStore : 
    IUserStore<User, int>, 
    IUserPasswordStore<User, int>, 
    IUserLockoutStore<User, int>, 
    IUserTwoFactorStore<User, int>, 
    IUserRoleStore<User, int>, 
    IUserClaimStore<User, int> 
{ 

    // You can inject connection string or db session 
    public UserStore() 
    { 
    } 

} 

Ich habe nicht alle Methoden für jede Schnittstelle enthalten. Sobald Sie getan haben, dass Sie in der Lage sein werden, den neuen Benutzer zu schreiben:

public System.Threading.Tasks.Task CreateAsync(User user) 
{ 
} 

es durch Id holen:

public System.Threading.Tasks.Task<User> FindByIdAsync(int userId) 
{ 
} 

und so weiter.

Dann müssen Sie Ihre SignInManager erben von Microsoft.AspNet.Identity.Owin.SignInManager definieren.

public class SignInManager: SignInManager<User, int> 
{ 
    public SignInManager(UserManager userManager, IAuthenticationManager authenticationManager): base(userManager, authenticationManager) 
    { 
    } 

    public override Task SignInAsync(User user, bool isPersistent, bool rememberBrowser) 
    { 
     return base.SignInAsync(user, isPersistent, rememberBrowser); 
    } 
} 

Ich habe nur SignInAsync umgesetzt: es wird erzeugt ein ClaimsIdentity.

Das ist es ziemlich viel.

Jetzt in Ihrer Startup Klasse müssen Sie Owin, wie Sie die UserManager und die SignInManager erstellen.

app.CreatePerOwinContext<Custom.Identity.UserManager>(() => new Custom.Identity.UserManager(new Custom.Identity.UserStore())); 
// app.CreatePerOwinContext<Custom.Identity.RoleManager>(() => new Custom.Identity.RoleManager(new Custom.Identity.RoleStore())); 
app.CreatePerOwinContext<Custom.Identity.SignInService>((options, context) => new Custom.Identity.SignInService(context.GetUserManager<Custom.Identity.UserManager>(), context.Authentication)); 

Ich habe nicht die Fabriken verwendet, um Sie in der Standardvorlage Ursache finden wollte ich wie möglich die Dinge so einfach zu halten.

Und aktivieren Sie Ihre Anwendung das Cookie zu verwenden:

app.UseCookieAuthentication(new CookieAuthenticationOptions 
{ 
    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie, 
     LoginPath = new PathString("/Account/Login"), 
     Provider = new CookieAuthenticationProvider 
     { 
     // Enables the application to validate the security stamp when the user logs in. 
     // This is a security feature which is used when you change a password or add an external login to your account. 
     OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<Custom.Identity.UserManager, Custom.Identity.User, int>(
     validateInterval: TimeSpan.FromMinutes(30), 
     regenerateIdentityCallback: (manager, user) => 
     { 
     var userIdentity = manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie); 
       return (userIdentity); 
    }, 
     getUserIdCallback: (id) => (Int32.Parse(id.GetUserId())) 
     )} 
}); 

Jetzt in Ihrem Konto Controller - oder den Controller verantwortlich für die Login - Sie erhalten die UserManager und die SignInManager bekommen müssen:

public Custom.Identity.SignInManager SignInManager 
{ 
    get 
    { 
    return HttpContext.GetOwinContext().Get<Custom.Identity.SignInManager>(); 
    } 
} 

public Custom.Identity.UserManager UserManager 
{ 
    get 
    { 
    return HttpContext.GetOwinContext().GetUserManager<Custom.Identity.UserManager>(); 
    } 
} 

Sie die SignInManager für die Anmeldung verwenden:

var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false); 

und die UserManager den Benutzer zu erstellen, fügen Sie Rollen und Ansprüche:

if (ModelState.IsValid) 
{ 
     var user = new Custom.Identity.User() { UserName = model.Email }; 

     var result = await UserManager.CreateAsync(user, model.Password); 
     if (result.Succeeded) 
    { 
     // await UserManager.AddToRoleAsync(user.Id, "Administrators"); 
       // await UserManager.AddClaimAsync(user.Id, new System.Security.Claims.Claim(System.Security.Claims.ClaimTypes.Country, "England")); 

       await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false); 

     return RedirectToAction("Index", "Home"); 
    } 
     AddErrors(result); 
} 

Es scheint kompliziert ... und es ist ... Art. Wenn Sie mehr darüber lesen möchten, gibt es eine gute Erklärung here und here.

Wenn Sie etwas Code ausführen und sehen möchten, wie es funktioniert, habe ich einige code zusammengestellt, die mit Biggy arbeitet (wie ich nicht zu viel Zeit verlieren wollte, um Tabellen und solche Sachen zu definieren).

Wenn Sie die Möglichkeit haben, meinen Code aus dem Github Repo herunterzuladen, werden Sie feststellen, dass ich ein sekundäres Projekt (Custom.Identity) erstellt habe, wo ich alle meine ASP.NET Identity Zeug gehalten habe.

Die einzige nuget Pakete, die Sie es benötigen, sind:

  1. Microsoft.AspNet.Identity.Core
  2. Microsoft.AspNet.Identity.Owin
+0

Erstaunliche Antwort - vielen Dank. SO viel Code aber für etwas relativ einfaches. Was ist der Treiber für all das? Ist es der App möglich, nicht zu wissen, * wo * die Daten tatsächlich sitzen, z. bessere Interoperabilität? – EvilDr

+1

Stimmen Sie mit Ihnen überein. Ich frage mich, ob wir wirklich alle diese Schritte durchlaufen müssen, um etwas zu implementieren, das einfach sein sollte. Der Treiber soll eigene Tabellen/Schemata für die Sicherheit haben.Ich - persönlich - mag Entitätsrahmen nicht und will es loswerden. Erweiterbarkeit vielleicht. Irgendwann möchten Sie vielleicht zu einigen NoSql-Datenbanken wechseln. – LeftyX

+1

Stimmen Sie sich auf Entity Framework ab. Aus Interesse, wie sind Sie mit dem obigen Code so vertraut geworden? Hat dich jemand in die richtige Richtung gelenkt und du hast es aus Erfahrung aufgenommen? Ich finde, dass ich am Tag nicht genug Zeit habe, um mit den schnellen Änderungen von ASP.NET Schritt zu halten ... – EvilDr