一、前言
在前一篇文章已經為大家介紹了OWIN和Katana,有了對他們的了解之后,才能更好地去學習Asp.net Identity,因為Asp.net Identity的實現集成了Owin。其實在Asp.net 2.0的時候,微軟已經對用戶權限管理進行了實現,其實現為Membership。由於之前的實現有很多限制,所以微軟在Asp.net 4.5推出了Asp.net Identity。接下來,本篇文章將詳細介紹下Asp.net Identity的實現。
二、Asp.net中用戶權限管理發展歷程
在前面我們已經說過,在Asp.net 2.0的時候,Asp.net中就已經實現了用戶權限管理,所以,Asp.net 用戶權限管理有其發展歷程。下圖就是Asp.net中權限管理的發展歷程:
ASP.NET Membership
Asp.net Membership是在2005年的Asp.net 2.0引入的。Membership機制引入了表單驗證(Form Authentication),以及一個用於存儲用戶名、密碼和其他用戶信息的SQL Server數據庫。但它同樣存在一些限制:
- 數據庫只能使用SQL Server,難以對SQL Server Compact、SQL Azure、NoSQL支持。並且你想為用戶表添加額外字段的話,此時你只能創建一個User的附加表。對於開發者來說,不能很好地自定義用戶信息。
- 由於Asp.net Membership是基於表單進行驗證的,因此無法支持OWIN。
ASP.NET Simple Membership
Asp.net Simple Membership是對Asp.net Membership的一次改進,它使得你可以更容易自定義用戶信息。盡管如此,由於它依然是基於Asp.net Membership之上的,所以它仍然存在以下幾點限制:
- 對非關系數據庫支持不好。
- 不支持OWIN
- 對於已存在的Asp.net Membership Provider支持的不是很好,不利於擴展。
ASP.NET Universal Providers
Asp.net Universal Providers解決了前兩者的一些問題,例如他支持存儲用戶在Azure SQL和SQL Server Compact數據庫中。並且它基於EF code First實現的,所以它支持EF支持的所有數據庫。但由於它依然是基於Asp.net Membreship基礎架構實現的,所以仍然有些問題不能很好解決。所以它只解決了前兩者的部分問題,其本身還存在一些限制:
- 對非關系數據庫支持不好
- 不支持OWIN
三、Asp.net Identity 詳細介紹
隨着互聯網的快速發展,從而非關系數據庫也層出不窮,但之前的三者權限管理都對非關系數據庫支持的不是很好,所以微軟必須要實現一種新的權限管理機制,所以在。NET Framework中推出了Asp.net Identity。該套機制解決了之前的所有問題。Asp.net 具有如下特點:
- 可用於ASP.NET所有框架上,包括Asp.net MVC、Asp.net Web Forms、Web Pages、Asp.net Web API和SignalR。
- 可用於各種應用程序,包括Web應用、移動應用,Windows Store應用和混合架構應用。
- 用戶信息的自定義
- 存儲易於擴展:默認使用EF Code First存儲在SQL Server數據庫中,但可以很好地擴展到SharePoint、Azure SQL和NoSQL 數據庫中。
- 支持單元測試
- 提供了Role Provider,使創建和管理變得簡單
- 支持面向Claims的身份驗證(即:支持基於聲明的身份驗證),前面的三者都是基於表單的身份驗證。
- 支持社交賬號的登錄,支持Facebook,Microsoft賬戶、Twitter,Google、QQ等社交賬戶。
- 支持Windows Azure Active Directory賬號登錄功能
- 支持OWIN。
- 通過Nuget發布,這樣能讓Asp.net 團隊更好地修復Bug和迭代新功能,並在第一個時間進行發布。將其與System.Web.dll程序集解耦。
四、Asp.net Identity內部實現機制
從上面對Asp.net Identity的介紹可以發現,它確實解決了之前的所有問題,那它是如何做到的呢?要知道其實現機制並不難,因為Asp.net Identity已經開源,我們可以到其站點下載其源碼研究即可,其開源地址為:http://aspnetidentity.codeplex.com/。這里簡單的分析它注冊和登錄的功能的內部實現。
首先使用VS2013創建一個Asp.net MVC站點,此時網站的用戶授權和認證模塊的代碼的實現VS已經幫我們添加好了,我們只需要找到對應的注冊和登錄功能對其進行分析,從而明白Asp.net Identity是如何幫完成這兩個功能的。
首先,我們找到Accout控制器中注冊功能的實現代碼:
public async Task<ActionResult> Register(RegisterViewModel model) { if (ModelState.IsValid) { var user = new ApplicationUser { UserName = model.Email, Email = model.Email }; var result = await UserManager.CreateAsync(user, model.Password); if (result.Succeeded) { await SignInManager.SignInAsync(user, isPersistent:false, rememberBrowser:false); // 有關如何啟用帳戶確認和密碼重置的詳細信息,請訪問 http://go.microsoft.com/fwlink/?LinkID=320771 // 發送包含此鏈接的電子郵件 // string code = await UserManager.GenerateEmailConfirmationTokenAsync(user.Id); // var callbackUrl = Url.Action("ConfirmEmail", "Account", new { userId = user.Id, code = code }, protocol: Request.Url.Scheme); // await UserManager.SendEmailAsync(user.Id, "確認你的帳戶", "請通過單擊 <a href=\"" + callbackUrl + "\">這裏</a>來確認你的帳戶"); return RedirectToAction("Index", "Home"); } AddErrors(result); } // 如果我們進行到這一步時某個地方出錯,則重新顯示表單 return View(model); }
從上面代碼的方法名可以看出,完成用戶注冊的主要實現在於UserManager.CreateAsync方法上,這個方法實現真是在Asp.net Identity幫我們實現,接下來到我們下載的源碼來查看該方法的實現。具體的源碼實現如下所示:
public virtual async Task<IdentityResult> CreateAsync(TUser user, string password) { ThrowIfDisposed(); var passwordStore = GetPasswordStore(); if (user == null) { throw new ArgumentNullException("user"); } if (password == null) { throw new ArgumentNullException("password"); } // UpdatePassword對密碼進行Hash加密 var result = await UpdatePassword(passwordStore, user, password).WithCurrentCulture(); if (!result.Succeeded) { return result; } // 注冊功能的實現 return await CreateAsync(user).WithCurrentCulture(); } public virtual async Task<IdentityResult> CreateAsync(TUser user) { ThrowIfDisposed(); await UpdateSecurityStampInternal(user).WithCurrentCulture(); var result = await UserValidator.ValidateAsync(user).WithCurrentCulture(); if (!result.Succeeded) { return result; } if (UserLockoutEnabledByDefault && SupportsUserLockout) { await GetUserLockoutStore().SetLockoutEnabledAsync(user, true).WithCurrentCulture(); } // 調用IUserStore的CreateAsync完成用戶注冊 await Store.CreateAsync(user).WithCurrentCulture(); return IdentityResult.Success; } // UserStore中CreateAsync的實現 public virtual async Task CreateAsync(TUser user) { ThrowIfDisposed(); if (user == null) { throw new ArgumentNullException("user"); } // 將實體添加進DbSet<User>集合中 _userStore.Create(user); // 調用SaveChanges將用戶保存到數據庫中 await SaveChanges().WithCurrentCulture(); }
看到這里是不是豁然開朗了很多的,其實Asp.net Identity內部注冊功能的實現,我們完全可以自己來實現。其實現簡單的說就是:
1. 對用戶提交的數據進行驗證
2. 對密碼進行加密保存
3. 調用Microsoft.AspNet.Identity.EntityFramework命名空間下的UserStore類的CreateAsync方法將用戶進行持久化。
4. UserStore類中的CreateAsync方法的實現也就是DbSet<User>.Add(entity)和SaveChanges()方法將對象持久化。
到這里還有一個問題,其實上面代碼調用的是IUserStore接口中的CreateAsync方法,但具體的IUserStore對象是怎么注入進去的呢?
你帶着這個疑惑去Asp.net MVC站點中去尋找其注入代碼。此時,你可以發現在Startup.Auth.cs和Start.cs文件中有如下實現:
// Startup.cs 文件 public partial class Startup { public void Configuration(IAppBuilder app) { ConfigureAuth(app); } } // Start.Auth.cs文件 public void ConfigureAuth(IAppBuilder app) { // 配置數據庫上下文、用戶管理器和登錄管理器,以便為每個請求使用單個實例 // ......... app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create); // ........ } // IdentityConfig.cs文件 public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context) { var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>())); // ........ }
看到上面標注紅色的代碼了嗎,這里就是將UserStore注入的地方。看到這里,你是不是沒有任何疑惑了。對於登錄功能的實現大家同樣可以按照這樣的方法去探索。本來想一起分析下的,后面想想,還是留給大家去探索吧。
五、從Asp.net Identity內部實現學會項目分層架構
其實,在我們平時工作,只要學會如何使用Asp.net Identity機制來完成對應功能。那我們為什么還要研究其源碼實現呢?我覺得有兩點:
- 研究源碼實現,可以讓你對其實現原理有一個深刻的理解,對於分析出現的問題有極大的好處。因為只有你了解其實現原理,寫功能模塊才能更加自信,處理出現的問題才會比別人快。
- 除了第一點之外,研究源碼還有一個重要的作用就是學習源碼作者的項目分層和代碼分離。在現實生活中,有很多朋友抱怨出現瓶頸了,無法提高,因為平常工作中一般都是去寫堆功能的代碼,覺得對能力沒什么提高。此時你完全可以去研究微軟開源的代碼,通過研究源碼來學習大牛們是如何將項目做到低耦合高內聚的,學習大牛們是如何做到代碼分離的。然后再講學習到的內容應用於工作,相信這樣的一個過程下來,你不想提高都不行了。漸漸地你會覺得自己也可以完成一個開源框架。
上面介紹了研究源碼的兩大作用,那我們從Asp.net Identity內部實現中又學到了什么呢?
通過第四部分的代碼分析,Asp.net Identity中注冊功能的實現主要分為的4點中,我們可以學到如下幾點:
- 關注點的分離。Asp.net Identity注冊功能中,將用戶輸入以及密碼加密等代碼實現都分離到具體的類中進行實現,而不是將其放在UserManager這個類中。這充分體現關注點分離原則
- 針對接口編程原則。Asp.net Identity內部實現中,都是針對於接口編程,每個類中依賴都是接口,並沒有依賴與具體類。從而降低代碼之間的耦合。
- 實現了依賴注入。Asp.net Identity具體實現是通過在調用端通過依賴注入的方式進行注入。
- 項目分層架構。Asp.net Identity注冊功能。AccountController首先調用UserManager的CreateAsync,而UserManager的CreateAsync又調用了IUserStore中的CreateAsync方法來通過調用EF的DbContext來完成數據的持久化。從這個調用過程和類之間的關系可以看出,這真是領域驅動設計的分層體現。領域驅動設計中設計4層,分別是UI層、應用層、領域層和基礎設施層。其中UI層對應的就是AccountController類,應用層對應的就是UserManager類、領域層就是具體的User實體、基礎設施層對應的就是IUserStore(准確地說,基礎設施層中的倉儲對應着IUserStore)。上面對應的項目分層,其實每個層中代碼的實現都可以按照這個模式去實現。這點在ABP Web框架中得到了很好的實現:https://github.com/aspnetboilerplate/aspnetboilerplate。
所以,如果你覺得你現在的工作得到提高的話,完全不需要去什么群里咨詢其他的推薦什么書籍什么,從現在開始就開始研究源碼吧。如果不知道研究什么源碼的話,完全可以從微軟的一些開源代碼開始,例如就從Asp.net Identity源碼開始,不要擔心研究完之后,還是怕不能提高,只要你理解和領悟了,你不想提高都難。另外再推薦大家研究下ABP的實現,我最近就在研究它,希望理解透徹之后,再寫一個小的Web框架來鞏固自己的研究。到時候也會把自己的一些研究心得分享到這里。
六、總結
到這里,本篇文章的介紹就結束了,希望這篇文章可以幫助朋友對微軟的用戶權限管理框架有進一步的了解,以及希望哪些想提高的朋友,從現在開始就來和我一起來研究微軟的開源框架和ABP框架吧。記得研究之后,分享到這里與大家共享哦。