英文原文:http://tech.trailmax.info/2014/08/aspnet-identity-and-owin-who-is-who/
最近我發現Stackoverflow上有一個非常好的問題.提問者問:為什么在調用AuthenticationManager.SignIn后,claim仍然可以被添加到Identity並持久化到cookie里.
示例代碼如下所示:
ClaimsIdentity identity = UserManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie ); var claim1 = new Claim(ClaimTypes.Country, "Arctica"); identity.AddClaim(claim1); AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true }, identity ); var claim2 = new Claim(ClaimTypes.Country, "Antartica"); identity.AddClaim(claim2);
是的,為什么claim2在cookie已經設置完成后還可用.
在深入研究后,我發現AspNet Identity框架不設置cookie,而OWIN會設置,OWIN是Katana開源項目的一部分.有源碼可用是一件好事--你可以發現為什么事情有或沒有按你預期的方式工作.
在這個案例里,我花了一些時間探索Katana項目和 AuthenticationManager 工作方式.結果證明SignIn方法不設置cookie.它把Identity對象保存在內存里,直到設置響應cookies的時刻到來,然后claims被轉化為一個cookie,所有的事情就這樣魔法般地工作着 -)
這又引發了另一個問題.現下Identity沒有開源的代碼,所以OWIN在Identity中扮演什么角色,Claims又是如何工作的?
結果證明Identity框架只處理user持久化,密碼哈希,驗證密碼是否正確,發送密碼重置郵件,等等.但是Identity實際上不驗證users或創建cookies.而Cookies是被OWIN處理的.
看一下登錄的代碼:
public async Task SignInAsync(Microsoft.Owin.Security.IAuthenticationManager authenticationManager, ApplicationUser applicationUser, bool isPersistent) { authenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie); ClaimsIdentity identity = await UserManager.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie); authenticationManager.SignIn(new Microsoft.Owin.Security.AuthenticationProperties() { IsPersistent = isPersistent }, identity); }
Identity只創建ClaimsIdentity(學習網站 ReferenceSource ),而ClaimsIdentity是.Net framework的一部分,而不是來自於互聯網的nuget包.然后這個ClaimsIdentity被傳給擁有一個設置cookies回調的OWIN的AuthenticationManager,而AuthenticationManager擁有一個在寫響應頭時設置cookies的回調.
到目前為止都很好,已有三部分:Identity框架創建一個ClaimsIdentity,OWIN根據這個ClaimsIdentity創建一個cookie,和.Net framework掌控ClaimsIdentity的類.
當在你的類中要訪問ClaimsPrincipal.Current時,你只用到.Net framework,不需要用到其它類庫,這是非常方便的!
默認的Claims
Identity框架為你做了一件很漂亮的事,默認情況下當你登錄時,它為一個principal添加了一些claims,如下所示:
- User.Id:類型為“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier” 或ClaimTypes.NameIdentifier.
- Username:類型為“http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name” 或ClaimTypes.Name.
- "ASP.NET Identity":保存為“http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider“.這在你使用OpenId做驗證時非常有用.不過如果只是使用數據庫存儲users時沒什么用.點擊查看更多信息.
- 包含user的安全郵戳的Guid,在Claim中持久化類型為“AspNet.Identity.SecurityStamp“.安全郵戳是user狀態的一個主要快照,如果驗證的密碼/方法,email,等等發生變化,安全郵戳就會發生變化,這允許你通過改變證書實現"在任何地方登出".從Kung的回答中獲取更多有關安全郵戳的信息.
- 最有用的claims是role.所有分配給user的role被保存成ClaimTypes.Role或“http://schemas.microsoft.com/ws/2008/06/identity/claims/role“.所以下次你需要檢查當前user的roles,檢查這個claims,不會到數據庫中查找,這樣非常快.實際上,如果你調用ClaimsPrincipal的.IsInRole("RoleName"),框架會進入claims並檢查用戶是否分配了這個指定值的Role.
你可以在.Net Reference 網站查看這些claim類型,這個列表不是完整的,你可以創建你自己的claim類型--就是一個string.
如果你想添加你自己的owin claim類型,我建議你使用自己的符號,例如:“MyAppplication:GroupId” ,並保持所有的claim類型作為常量在一個類中:
public class MyApplicationClaimTypes { public string const GroupId = "MyAppplication:GroupId"; public string const PersonId = "MyAppplication:PersonId"; // other claim types }
這種方式,你總是可以找到claims,並不會與框架中的claim類型沖突,除非你的claims與框架中的claims類型完全一致,例如:ClaimTypes.Email.
添加默認的claims
我總是在user登錄里,添加user的email到claims列表中,就如最前面示例里的claim1和claim2:
public async Task SignInAsync(IAuthenticationManager authenticationManager, ApplicationUser applicationUser, bool isPersistent) { authenticationManager.SignOut( DefaultAuthenticationTypes.ExternalCookie, DefaultAuthenticationTypes.ApplicationCookie); var identity = await this.CreateIdentityAsync(applicationUser, DefaultAuthenticationTypes.ApplicationCookie); // using default claim type from the framework identity.AddClaim(new Claim(ClaimTypes.Email, applicationUser.Email)); authenticationManager.SignIn(new AuthenticationProperties() { IsPersistent = isPersistent }, identity); }
你可以在這里為所有user添加默認的claims,但有一個IClaimsIdentityFactory類(賦給UserManager),只有一個方法:
public interface IClaimsIdentityFactory<TUser, TKey> where TUser : class, IUser<TKey> where TKey : IEquatable<TKey> { /// <summary> /// Create a ClaimsIdentity from an user using a UserManager /// </summary> Task<ClaimsIdentity> CreateAsync(UserManager<TUser, TKey> manager, TUser user, string authenticationType); }
AspNet Identity的默認實現是:創建ClaimsIdentity,添加如上所述的默認claims,為user在數據庫中存儲IdentityUserClaims類型的claims.你可以重寫這個實現,並插入你自己的邏輯/claims:
public class MyClaimsIdentityFactory : ClaimsIdentityFactory<ApplicationUser, string> { public override async Task<ClaimsIdentity> CreateAsync(UserManager<ApplicationUser, string> userManager, ApplicationUser user, string authenticationType) { var claimsIdentity = await base.CreateAsync(userManager, user, authenticationType); claimsIdentity.AddClaim(new Claim("MyApplication:GroupId", "42")); return claimsIdentity; } }
然后在賦給UserManger:
public UserManager(MyDbContext dbContext) : base(new UserStore<ApplicationUser>(dbContext)) { // other configurations // Alternatively you can have DI container to provide this class for better application flexebility this.ClaimsIdentityFactory = new MyClaimsIdentityFactory(); }