ASP.NET Identity除了提供基於Cookie的身份驗證外,還提供了一些高級功能,如多次輸入錯誤賬戶信息後會鎖定用戶禁止登錄、集成第三方驗證、賬戶的二次驗證等,並且ASP.NET MVC的預設模板中就帶有這些功能。 本文將從以下幾個方面解釋ASP.NET Identity是如何實現身份驗證 ...
ASP.NET Identity除了提供基於Cookie的身份驗證外,還提供了一些高級功能,如多次輸入錯誤賬戶信息後會鎖定用戶禁止登錄、集成第三方驗證、賬戶的二次驗證等,並且ASP.NET MVC的預設模板中就帶有這些功能。
本文將從以下幾個方面解釋ASP.NET Identity是如何實現身份驗證機制的:
● ASP.NET Identity的“多重”身份驗證
● Owin身份驗證的積極模式與消極模式
● 再談Owin身份驗證機制
● 基於Owin的Identity在ASP.NET中身份驗證的解決方案
ASP.NET Identity的“多重”身份驗證
為什麼本章以Identity的多重身份驗證為題?因為ASP.NET Identity是基於Owin通過中間件的形式實現的身份驗證,以下是預設模板中添加的代碼:
從代碼中可以看到,模板代碼一共添加了7個(包含被註釋的3個)身份驗證相關的中間件,按照對Owin中間件的理解,當一個請求進入Owin管道後,每一個中間件都會被執行,上面的身份驗證中間件按照功能來分有基於Cookie的、有基於外部Cookie的、雙因數驗證的以及第三方賬戶登錄(被註釋的代碼)的,這就意味著每一個請求都會被這些中間件一一處理,所以稱其為“多重”身份驗證。
本文從標題到現在的“多重”均是打引號的,說明Identity中的驗證並不像錶面那樣多個功能堆疊那麼簡單,接下來就從身份驗證的積極模式與消極模式開始來消除Identity在ASP.NET身份驗證中的迷霧。
Owin身份驗證的積極模式與消極模式
Identity是基於Owin實現身份驗證的,所以實際上Owin才是身份驗證規則的制定者,Identity只不過是實現的一種,在Owin中把身份驗證中間件分為兩個模式,分別是積極模式與消極模式,兩個模式是通過一個名為AuthenticationMode的枚舉類型定義的:
根據代碼註釋得到對積極模式以及消極模式的介紹如下:
● 積極模式(Active):積極模式的身份驗證中間件將在請求到達時對用戶身份信息進行修改,同時當響應狀態為401時會處理響應信息。
● 消極模式(Passive):該模式下的中間件只有需要或者說顯式調用的情況下才會使用該中間件包含的方法對請求進行驗證,包括當出現401情況時也需要在拓展的Challenge數據中找到對應的驗證類型名稱匹配後才會調用處理。
一句話來說就是無論在Owin管道中添加多少個身份驗證中間件,在處理請求時都只有驗證模式為積極的才會對請求進行處理,其它的都需要通過手動的方式來調用。為什麼?看後面內容。
再談Owin身份驗證機制
前面的文章中介紹了Owin與Identity是如何集成的如《ASP.NET沒有魔法——Identity與Owin》、《ASP.NET沒有魔法——ASP.NET Identity的加密與解密》等文章,但主要是介紹Identity如何通過中間件的方式添加到Owin管道對用戶身份信息驗證以及基於Cookie的身份驗證中間件做了什麼工作,最核心的點還未提到,那就是整個身份驗證的機制或者說規則是由Owin(Katana)來定的,Identity只不過是遵循這個規則的一個實現甚至可以說只是一個使用者,因為它僅僅是為Cookie驗證中間件提供了用戶數據,然後通過依賴AuthenticationManager來實現、拓展了ASP.NET中的身份驗證功能。
上面是Owin身份驗證相關的包圖,從這個圖中可以得出以下結論:
1. Micrsosft.Owin中定義了Owin相關的主體如上下文、管道Builder以及身份驗證相關的業務邏輯AuthenticationManager。
2. Microsoft.Owin.Security作為Owin安全相關功能的補充,定義了身份驗證的模式(積極和消極)以及用於身份驗證的中間件和處理器基類。
3. Microsoft.Owin.Security.XXX類型作為真實的身份驗證邏輯實現者提供了各種不同的基於Cookie的、Token的甚至第三方賬戶等的身份驗證邏輯。
4. Microsoft.AspNet.Identity.Owin有兩個主要對象,SignInManager封裝了登錄時的業務邏輯,這個業務邏輯既包括了用戶數據的操作(UserManager)和身份驗證的邏輯,提供了普通登錄、雙因數登錄、外部賬戶登錄等高級功能,而AuthenticationManagerExtensions同樣也是針對這些高級功能對AuthenticationManager的拓展。
AuthenticationManager
AuthenticationManager作為Owin中身份驗證的業務的核心,其介面定義如下:
根據註釋看出它用於與串聯到管道的身份驗證中間件進行交互,並且提供了身份驗證AuthenticateAsync、拒絕Challenge(我將其翻譯拒絕或質疑等意思,換句話就是身份驗證未通過)、登入SignIn、登出SignOut以及獲取驗證方式GetAuthenticationTypes等方法。
對於AuthenticationManager可以這麼理解,每一次請求都會創建一個AuthenticationManager,它攜帶了當前請求的用戶信息User,然後管理所有串聯到Owin管道的身份驗證中間件,通過這一系列的中間件完成了用戶的驗證與拒絕、登入與登出等功能。
需要註意的是AuthenticateAsync返回的是一個驗證結果,該結果中包含除了驗證結果外還包含了用戶信息,而SignIn方法實際上是將用戶信息添加到(或者說替換)當前請求上下文的用戶信息,所以大部分情況下AuthenticateAsync和SignIn方法是連續使用的。
AuthenticationMiddleware&AuthenticationHandler
AuthenticationMiddleware和AuthenticationHandler作為模板代碼,對中間件的執行以及處理器的處理方法進行了限制:
AuthenticationMiddleware的執行方法:
分為四個階段:1. 處理器的創建。2. 處理器的初始化。3. 處理器的調用。4. 處理器的銷毀。
對於AuthenticationHandler來說,它有幾個重要的過程:
1. 初始化:將當前處理器註冊到AuthenticationManager中,另外如果當前中間件的模式為積極模式,那麼調用身份驗證方法,否則身份驗證方法就只能通過AuthenticationManager調用。(註:InitializeCoreAsync的具體實現在子類中)
2. 執行:
從上面的分析可以知道,在身份驗證處理其中有一個專門用於身份驗證的方法AuthenticateAsync,那麼執行方法是做什麼用的呢?在基於Identity的ASP.NET應用程式中身份驗證方法只有在積極模式下時才會對自動對請求進行身份驗證,並且無論是否通過驗證都不會對請求進行任何的處理(即使沒有通過身份驗證仍舊能夠將請求送到Controller、Action的執行,因為它們是可以被匿名調用的),而有一種情況就是當伺服器處理請求時就能夠確定該請求應該如何處理,如通過第三方賬戶登錄後會在Url的查詢字元串上攜帶一些Token等信息,那麼這個時候能夠確定的就是需要根據這些信息進行處理進而完成身份驗證而不是還可以將請求交給Controler處理,在這種情況下就可以將這些處理的邏輯放到Invoke方法中,Invoke方法如果返回true時,管道將不再繼續往下執行(見中間件的Invoke方法)。微軟、Google等社交賬戶驗證時,它的驗證邏輯是寫在這個InvokeAsync方法中的,而Cookie驗證的中間件就預設返回false將請求交與後續組件處理,後續詳細介紹Owin的第三方驗證。
3. 銷毀:當請求處於返回階段時,會觸發身份驗證處理器的銷毀過程,整個過程包括將身份信息寫入響應信息中(如Cookie驗證會將AuthenticationTicket對象序列化加密後寫到Cookie中),並執行子類的一些銷毀邏輯,最後將添加到AuthenticationManager中的處理器刪除。
SignInManager&AuthenticationManagerExtensions
SignInManager用於管理用戶的登錄邏輯,如登錄、使用密碼登錄、雙因數登錄、外部登錄等等,它是對UserManager與AuthenticationManager的封裝:
AuthenticationManagerExtensions是對AuthenticationManager的拓展,主要是對AuthenticationManager添加了外部驗證以及雙因數驗證的補充支持,這些拓展方法被SignInManager以及身份驗證Controller使用:
基於Owin的Identity在ASP.NET中身份驗證的解決方案
上面介紹了Owin以及Identity中主要的組件及其功能,那麼在ASP.NET中是如何使用這些功能實現身份驗證功能的呢?在ASP.NET中主要把身份驗證分為這三種類型:
● 普通驗證:普通驗證就是之前文章中介紹過的基於Cookie的身份驗證方式,其過程是用戶提交用戶名及密碼,伺服器完成密碼驗證後生成用戶信息,後續請求根據這個用戶信息即可驗證用戶身份。
● 雙因數驗證:雙因數驗證在普通驗證基礎上增加瞭如簡訊驗證碼、郵件驗證碼等信息的二次驗證,就是說伺服器根據用戶名密碼完成用戶驗證後,該用戶仍然處於“未通過”的身份驗證狀態,還需要對發送給客戶的驗證碼進行二次校驗後才完成身份驗證。
● 外部驗證:既用戶數據在應用程式外部,如各種不同的社交賬戶,通過這些服務商提供的身份驗證介面登陸後,返回一系列的服務訪問Token、用戶信息等的驗證方式。
這裡將通過ASP.NET MVC帶身份驗證功能的模板代碼來介紹以上三種方式是如何使用Identity組件實現的。
普通驗證
Identity在ASP.NET中通過Cookie驗證的方式實現了普通的用戶身份驗證,其主要過程如下(註:左邊為主要流程,右邊為每一個主流程包含的子流程):
上圖是ASP.NET MVC中通過Identity實現基於Cookie的用戶身份驗證的用戶信息生成過程,它有幾個需要註意的點:
1. 首先將用戶名、密碼等信息通過可匿名訪問的AccountControllter中的Login方法提交到伺服器,然後通過SignInManager的PasswordSignInAsync實現了用戶名、密碼的驗證以及登錄,最後在請求返回階段將用戶身份信息加密後寫到Cookie中。
2. 在密碼驗證/登錄階段除了對用戶密碼進行驗證外,還有一些用戶是否被鎖定、登錄失敗次數計數以及重置等判斷,用於實現用戶的鎖定以及多次登錄失敗自動鎖定用戶等功能。
3.SignInManager.SignInOrTwoFactor方法根據驗證方法的配置決定是雙因數驗證還是普通驗證,在普通驗證情況下,通過獲取用戶身份信息以及新建身份驗證屬性來完成後續的驗證管理器的登入操作(註:上面在介紹身份驗證管理器時說過,它的AuthenticateAsync和SignIn方法是連續使用的,前者獲取用戶信息,後者將用戶信息寫到請求上下文中,但是因為現在是登錄操作,請求中還沒有用戶身份信息,所以用戶身份信息以及驗證屬性需要自己創造,比如通過資料庫獲取,這裡通過自己創造的用戶信息然後調用SignIn方法實現了登錄將用戶信息寫到請求上下文中)。
4.當請求返回時,將寫到請求上下文中的用戶信息以Cookie的形式攜帶到客戶端,以便於後續請求的身份驗證。
上圖是當用戶通過登錄後,訪問其它資源時候的身份驗證過程,這裡整個處理過程都是Cookie驗證中間件完成的,需要註意的有以下幾點:
1. Cookie驗證中間件預設是積極模式,所以在處理器初始化時就會調用AuthenticateAsync方法,對請求中的身份信息Cookie進行解析、驗證。
2. 當完成身份信息的驗證後,該中間件通過AddUserIdentity這個方法將用戶信息寫到請求上下文中(該方法實際上是替換了AuthenticationManager的SignIn方法)。
3. 當請求返回階段,如果狀態正常那麼繼續將身份信息寫到Cookie中,但是如果身份驗證未通過(如超時等情況),請求就回被處理器的ApplyResponseChallengeAsync處理(如跳轉到登錄頁面等功能)。
雙因數驗證
雙因數驗證是在普通驗證的基礎上引入了第二次驗證,也就是說在登錄時會有兩次請求:
上圖就是雙因數驗證的過程,雙因數驗證實際上也是基於普通驗證的,它僅僅是通過配置的形式開啟,在身份驗證過程中加入了驗證碼的發送與校驗過程,它需要註意以下幾點:
1. 在首次登錄時和普通驗證一樣,需要對用戶密碼、是否被鎖定等信息進行驗證,然後因為需要雙因數驗證,所以創建了一個TwoFactorCookie的身份信息並執行了管理器的SignIn方法,該信息保存了通過密碼驗證的用戶名。完成請求後, app.UseTwoFactorSignInCookie方法添加Cookie驗證中間件將會把上面創建的TwoFactorCookie身份信息寫到Cookie中(註:在管道中添加了多個cookie的驗證中間件,但是它的配置和身份驗證類型是不同的,這裡是根據用戶信息的身份驗證方式來選擇使用哪一個中間件來處理生成Cookie,而ClaimsIdentity構造傳入的值就是身份驗證方式)。
2. ASP.NET MVC程式通過重定向的方式,將完成第一次驗證的請求轉到了驗證碼發送(選擇發送方式)及驗證碼填寫頁面,填寫完驗證碼並提交後進入第二次驗證。
3. 在第二次驗證過程中AccountController的VerifyCode方法通過SignInManager的TwoFactorSignInAsync方法完成了驗證,該方法的核心是通過AuthenticationManager顯式的調用了“TwoFactorCookie”的驗證,該驗證是 app.UseTwoFactorSignInCookie方法添加的一個消極模式的身份驗證中間件。(註:TwoFactorCookie常量的值就是TwoFactorCookie,AuthenticationManager的SignIn方法通過字元串匹配的形式來查找並調用對應的身份驗證方法)
這個驗證方法就是獲取上一次存儲的Cookie並解密後得到用戶名,獲得對應用戶信息後,通過UserTokenProvider對驗證碼進行校驗,當所有信息通過校驗後,將使用AuthenticationManager的SignIn方法將用戶信息添加到請求上下文中(註:關於驗證碼的生成和校驗在後續章節中介紹)。
4. 在VerifyCode請求響應的時候將用戶信息通過積極模式的Cookie中間件將用戶信息寫到Cookie中,該過程與普通驗證一致。
註:後續請求與普通驗證一致,使用Cookie攜帶的身份信息,通過積極模式的Cookie身份驗證中間件完成身份驗證。
外部驗證
外部驗證即將用戶的身份驗證過程轉交給外部伺服器(如各大社交平臺等),相對於上面兩種驗證的流程來說,外部驗證的流程更為複雜,其流程圖如下:
(註:由於流程圖太大,將其分為兩個部分,第一部分為第三方登錄頁面的跳轉,第二部分為第三方登錄完成後重定向回來的ASP.NET身份驗證過程)
ASP.NET第三方登錄頁面的調整流程比較簡單,在預設的模板項目中整個功能就是由AccountController中的ExternalLogin Action方法發起的,首先需要的就是在頁面上選擇一種第三方賬號登錄方式,提交到服務端後,ExternalLogin方法直接通過AuthencationManager的拒絕(Challenge)方法拒絕當前請求,將請求重定向到了第三方的登錄頁面。
當第三方登錄成功後會重定回AccountController的ExternalLoginCallback,整個流程的說明如下:
1. 首先需要提到的是模板項目在身份驗證管道中通過app.UseExternalSignInCookie方法設置了預設的登錄驗證方式(ExternalCookie)以及添加了一個消極模式的Cookie驗證中間件,其驗證方式也是ExternalCookie。
2. 由於請求中攜帶了由微軟(此處以微軟為例)身份驗證後的信息,所以會被特定身份驗證中間件的Invoke方法處理,其處理過程是先調用了對應的身份驗證方法獲得身份信息,將驗證後的身份信息添加預設的身份驗證類型(註:該預設類型就是ExternalCookie,其目的是為了將所有通過第三方身份驗證的賬戶統一處理),最後將身份信息通過SignIn方法寫到上下文中,並返回True(註:上面提到過,當身份驗證處理器的Invoke方法返回true時後續的內容將不會被調用)。
3. 在從微軟身份驗證處理中間件的返回過程中,由於上下文中的用戶信息的身份驗證模式是ExternalCookie,所以會被ExternalCookie這個中間件的ApplyResponseGrantAsync方法處理,將當前的身份信息序列化並加密後保存到名為.AspNet.ExternalCookie的cookie中,然後重定向到AcountController的ExternalLoginCallback。
4. ExternalLoginCallback方法實際才是ASP.NET中真正用來身份驗證的步驟,前面僅僅是通過第三方獲得驗證後的用戶名等信息,然後將該用戶的信息以cookie的形式保存到ExternalCookie中,而這裡首先就是通過AuthenticationManager調用了ExternalCookie中間件的身份驗證方法,獲得Cookie中保存的用戶信息,然後通過這個用戶信息去本地資料庫中查詢,如果用戶存在,那麼登錄成功,否則跳轉到ExternalLoginConfirmation頁面去補充用戶信息(相當於根據第三方用戶名完成註冊)後登錄,登錄成功後用戶信息會被積極模式的Cookie身份驗證中間件保存到.AspNet.ApplicationCookie中。(註:這裡的登錄過程可參考普通驗證的登錄過程)。
以上就是三種身份驗證的執行流程,雖然它們流程不同,但最後目的是一致的,在每一種身份驗證流程最後都是通過SignInManager的SignIn方法完成的登錄,即無論什麼方式登錄的,最終都會將身份信息以Cookie的形式保存在名為.AspNet.ApplicationCookie的Cookie中。並且該方法會將雙因數驗證以及外部Cookie驗證的信息都清除:
另外雙因數驗證作為一種附加的驗證方式,它既可以附加到普通驗證方式上也可以附加到第三方驗證方式上。
小結
本章是對Owin的身份驗證機制以及ASP.NET MVC基於Identity的身份驗證解決方案進行了介紹,ASP.NET MVC使用Identity基於Owin提供的三種驗證方式已經能夠滿足日常的開發需求,對這些流程的理解能夠更好的根據需求來完善自己項目中的身份驗證功能,在下一篇文章中將以代碼的形式在My Blog中添加雙因數驗證以及第三方賬戶驗證。
PS:個人覺得這些流程還是比較複雜,本章內容也只是對大體的流程進行了介紹,在實際代碼中還對身份驗證中可能出現的其它情況進行了處理,有興趣可通過反編譯的方式查看相應類型的源碼,總的來說ASP.NET為開發者提供了一個很強大的身份驗證功能,在開發時會想到幾句代碼實現的功能會有這麼複製嗎?另外感謝大家對我的支持,如有問題儘管提出,大家共同進步。下一篇會用代碼的方式介紹如何實現第三方賬戶登錄與雙因數驗證。(*^_^*)
參考
http://bitoftech.net/2015/01/21/asp-net-identity-2-with-asp-net-web-api-2-accounts-management/
https://www.cnblogs.com/XiongMaoMengNan/p/6785155.html
https://stackoverflow.com/questions/26166826/usecookieauthentication-vs-useexternalsignincookie
https://docs.microsoft.com/en-us/aspnet/web-api/overview/security/external-authentication-services
https://www.benday.com/2014/02/25/walkthrough-asp-net-mvc-identity-with-microsoft-account-authentication/
本文鏈接:http://www.cnblogs.com/selimsong/p/7903718.html