asp.net core 3.x 身份驗證-1涉及到的概念


前言

從本篇開始將圍繞asp.net core身份驗證寫個小系列,希望你看完本系列后,腦子里對asp.net core的身份驗證原理有個大致印象。
至於身份驗證是啥?與授權有啥聯系?就不介紹了,太啰嗦。你如果不曉得,自己去搜搜吧。
我的學習思路是詳細看源碼 > 總結得出一個宏觀上的印象 + 如何使用。
如果發現有啥講錯的望指正,免得誤導觀眾

我們偶爾會思考如何設計一個牛X的軟件,其實通過對asp.net core框架本身的學習更划算,一來我們熟悉了asp.net core框架,再者我們學習了微軟碰到需求是如何設計的。

計划:

  • 基本介紹 - 概述 + 核心類介紹
  • 基於cookie/session的身份驗證原理 - 適合瀏覽器
  • 基於Token身份驗證 - 適合移動端app
  • 集成第三方登錄原理 - 比如集成微信、支付寶登錄
  • IdentityServer - 目前不鳥解
  • asp.net core Identity - 目前不鳥解

必備知識:asp.net core、配置、選項、依賴注入、中間件等...

參考:源碼Artechmvc5基於owin的身份驗證視頻ASP.NET Core 運行原理解剖[5]:Authentication

注意:本篇只講涉及到的幾個概念

 

推薦個不錯的流程圖/腦圖工具:https://www.processon.com/i/59accdd8e4b0859febda28e3,點這個鏈接注冊我可以獲得幾個文件限額,抱拳~

身份驗證方式和簡易流程

常見的身份驗證方式:

  • 基於cookie/session的身份驗證 - 適合瀏覽器
  • 基於JWTToken身份驗證(OAuth2) - 適合移動端app
  • 集成第三方登錄(OAuth2) - 比如集成微信、支付寶登錄

為了便於理解后續的概念,下面先以最簡單常見的 【用戶密碼+cookie】 的身份驗證方式說說核心流程

登錄:

  1. 用戶輸入賬號密碼提交
  2. 服務端驗證賬號密碼
  3. 若驗證成功,則創建一個包含用戶標識的票證(下面會說)
  4. 將票證加密成字符串寫入cookie

攜帶cookie請求:

  1. 用戶發起請求
  2. 身份驗證中間件嘗試獲取並解密cookie,進而得到含用戶標識的票證(下面會說)
  3. 將用戶標識設置到HttpContext.User屬性

注意:若身份驗證中間件即使沒有解析得到用戶標識,請求也會繼續執行,此時以匿名用戶的身份在訪問系統

 

用戶標識ClaimsPrincipal

它用來表示當前登錄的用戶,它包含用戶Id + 一些與權限檢查相關的附件屬性(角色、所屬部門)
當請求抵達時“身份驗證中間件”將從請求中解析得到當前用戶,如果獲取成功則賦值給HttpContext.User屬性

所以對於我們來說通常有兩個場景使用它
在任意能訪問HttpContext的地方獲取當前用戶,比如在Controller中。
如果需要自定義實現身份驗證,則我們要想方設法從請求中解析得到用戶,並賦值給HttpContext.User

現在你至少對用戶標識這個概念有點理解了,如果要刨根問底兒就自行搜索關鍵字:asp.net Claims

也許你曾經做過或見過這樣的設計,定義Employee表示當前系統的用戶,當用戶登錄時會從數據庫查詢得到對應的Employee,若賬號密碼驗證通過則將其放入Session或緩存中。下次訪問時直接從Session/緩存中獲取當前用戶。個人覺得這種設計存在如下問題:

  • 浪費內存:我們的業務代碼訪問當前用戶最多的字段可能只是用戶id,性別、地址、聯系電話、學歷....這些字段不是每個業務處理都需要的
  • 拋棄了asp.net身份驗證框架:從asp.net 2.0時代微軟就設計了IPrincipal,后續的版本直到mvc5中基於owin的身份驗證都在使用此接口,后續的權限驗證微軟也提供了,也是基於此接口的,但我們放棄了,反而是自己有寫了一套微軟本身就實現的功能,可能多數是覺得自己寫的更簡單。但我覺得判斷哪種方式更合適是在你對兩種方式都了解的情況下再做出判斷。

 

用戶票證AuthenticationTicket

既然有了上面的用戶標識,何不直接在登錄時加密這個標識,解析時直接解密得到呢?因為我們還需要額外的控制,比如過期時間,這個屬性只是在身份驗證階段來判斷是否過期,在我們(如Controller.Action中)使用用戶標識的時候並不需要此字段,類似的額外字段根據不同的身份驗證方式可能有很多,因此定義了“用戶票證”這個概念,它包含 用戶標識 + 身份驗證過程中需要的額外屬性(如得到用戶標識的時間、過期時間等)

 

身份驗證處理器AuthenticationHandler

參考上面的用戶名密碼+cookie身份驗證流程我們發現有幾個核心的處理步驟:

  • 在登錄時驗證通過后將用戶標識加密后存儲到cookie,SignIn
  • 當用戶注銷時,需要清楚代表用戶標識的cookie,SignOut
  • 在登錄時從請求中獲取用戶標識,Authenticate
  • 在用戶未登錄訪問受保護的資源時,我們希望跳轉到到登錄頁,Challenge
    • Challenge叫做質詢/挑戰,意思是當發現沒有從當前請求中發現用戶標識是希望怎么辦,可能是跳轉到登錄頁,也可能是直接響應401,或者跳轉到第三方(如QQ、微信)的登錄頁 
  • 因為某種原因(如權限驗證不過),阻止方案,Forbid

身份驗證處理器就是用來跟身份驗證相關的步驟的,這些步驟在系統的不同地方來調用(比如在登錄頁對於的Action、在請求抵達時、在授權中間件中),
每個調用時都可以指定使用哪種身份驗證方案,如果不提供將使用默認方案來做對應的操作。
不同的身份驗證方式有不同的實現

IAuthenticationHandler接口只定義了最核心的幾個步驟:Authenticate()、Challenge()、Forbid()。登錄和注銷這兩個步驟定義了對應的子接口。當然微軟還為我們定義了抽象類,參考
   

 

某個具體的身份驗證方案的選項AuthenticationSchemeOptions

在上述身份驗證處理的多個步驟中會用到一些選項數據,比如基於cookie的身份驗證 cookeName、有效時長、再比如從請求時從cookie中解析得到用戶標識后回調選項中的某個回調函數,允許我們的代碼向調試中添加額外數據,或者干脆替換整個標識。

所以身份驗證選項用來允許我們控制AuthenticationHandler的執行。不同的身份驗證方式有不同的選項對象,它們直接或間接繼承AuthenticationSchemeOptions

 

身份驗證方案AuthenticationScheme

總結性的說:身份驗證方案 = 名稱 + 身份驗證處理器類型,暫時可以理解一種身份驗證方式 對應 一個身份驗證方案,比如:
基於用戶名密碼+cookie的身份驗證方式 對應的 身份驗證方案為:new AuthenticationScheme("UIDPWDCookie",typeof(CookieAuthenticationHandler))
基於用戶名密碼+token  的身份驗證方式 對應的 身份驗證方案為:new AuthenticationScheme("JwtBearer",typeof(JwtBearerHandler))

身份驗證方案在程序啟動階段配置,啟動后形成一個身份驗證方案列表。
程序運行階段從這個列表中取出指定方案,得到對應的處理器類型,然后創建它,最后調用這個處理器做相應處理
比如登錄操作的Action中xxx.SignIn("方案名") > 通過方案名找到方案從而得到對應的處理器類型 > 創建處理器 > 調用其SignIn方法

一種特殊的情況可能多種方案使用同一個身份驗證處理器類型,這個后續的集成第三方登錄來說

 

方案、處理器、選項、三者之間的關系

簡單但不准確的理解為:方案名+處理器+選項 = 身份驗證方式

 

身份驗證方案的容器AuthenticationSchemeProvider

身份驗證方案的容器(Dictionary<方案名,身份驗證方案>)
默認是單例形式注冊到依賴注入容器的
在應用啟動時通過AuthenticationOptions添加的各種身份驗證方案會被存儲到這個容器中
各種GetDefaultXXX用來獲取針對特定步驟的默認方案,如:GetDefaultAuthenticateSchemeAsync中間件從請求獲取用戶標識時用來獲取針對此步驟的默認方案、GetDefaultSignInSchemeAsync獲取默認用來登錄的方案、GetDefaultSignOutSchemeAsync...等等,身份驗證的不同步驟可以設置不同的默認方案。如果針對單獨的步驟沒有設置默認方案,則自動嘗試獲取總的默認方案,通過AuthenticationOptions設置這些默認值
身份驗證過程中各個步驟都會通過此對象拿到指定方案,並通過關聯的身份驗證類型獲得最終身份驗證處理器,然后做相應處理

整個應用的身份驗證選項AuthenticationOptions

AuthenticationSchemeBuilder的屬性跟AuthenticationScheme幾乎是一樣對應的,它的Build()方法會生成一個AuthenticationScheme,所以我們可以理解為AuthenticationSchemeBuilder = AuthenticationScheme

上面說的AuthenticationSchemeOptions是指某個具體身份驗證方案的選項,不同身份驗證方案有對應的子類,比如:CookieAuthenticationOptions、JwtBearerOptions.. 它們都是AuthenticationSchemeOptions的子類。AuthenticationOptions則是針對整個身份驗證功能的選項對象,我們需要在應用啟動階段通過它來配置身份驗證功能。可以把它理解為IDictionary<方案名, AuthenticationSchemeBuilder>(方案配置容器) + 一些默認值設置。

在應用啟動階段(Startup.ConfigreService)多次調用 AddScheme以添加身份驗證方案。

public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder)
{
    var builder = new AuthenticationSchemeBuilder(name);
    configureBuilder(builder);
    _schemes.Add(builder);
}

name方案名;configureBuilder允許我們提供委托對方案進行配置

添加的這些方案最終會被存儲到AuthenticationSchemeProvider供其使用

另外DefaultAuthenticateScheme、DefaultSignInScheme、DefaultSignOutScheme..看名字也曉得它是說當我們調用某個步驟未指定使用那個方案是的默認選擇

 

身份驗證處理器工廠AuthenticationHandlerProvider

它是以Scope的形式注冊到依賴注入容器的,所以每次請求都會創建一個實例對象。
唯一方法GetHandlerAsync從AuthenticationSchemeProvider獲取指定身份驗證方案,然后通過方案關聯的AuthenticationHandler Type從依賴注入容器中獲取AuthenticationHandler ,獲取的AuthenticationHandler會被緩存,這樣同一個請求的后續調用直接從緩存中拿。
所以也可以把它理解為AuthenticationHandler的運行時容器或工廠
AuthenticationService就是通過它來得到AuthenticationHandler然后完成身份驗證各種功能的

 

身份驗證服務AuthenticationService

身份驗證中的步驟是在多個地方被調用的,身份驗證中間件、授權中間件、登錄的Action(如:AccountController.SignIn())、注銷的Action(如:AccountController.SignOut()),身份驗證的核心方法定義在這個類中,但它本質上還是去找到對應的身份驗證處理器並調用其同名方法。其實這些方法還進一步以擴展方法的形式定義到HttpContext上了。以SignIn方法為例
HttpContext.SignIn() > AuthenticationService.SignIn() > AuthenticationHandler.SignIn() 

 

后續

這一篇只盡量簡單的說了下身份驗證涉及到的幾個核心概念,如果不明白的可以留言或等到下篇結合理解。下一篇將以用戶名密碼+cookie的身份驗證方式來詳細梳理下流程。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM