前面有說到 UserAuthentication() 跟 UserAuthorization(),這兩個的差別在於:前者用於驗證登入者是誰,後者則決定登入者可以做什麼。
舉例來說,一個員工要登入員工系統,他必須輸入帳號(如員工 ID、姓名或是 email)、密碼,系統才能知道是誰登入了,這就是 Authentication(驗證),處理 Authentication 的方式有 Cookie、Token、第三方驗證(OAuth 或 API-token)、OpenId 及 SAML。
而當員工登入系統後,一般員工通常不會有跨部門或是管理權限,他只能看到他自己或所屬部門的資訊,例如生產部員工看不到會計部的財務,不過會計部為了計算成本卻看得到生產部的原料價格,這就是 Authorization(授權)。
在決定登入者可以做什麼之前,必須先知道登入者是誰,所以 UserAuthentication() 必須放在 UserAuthorization() 前面。
ASP.NET Core Identity 使用的是基於 Claim 的驗證,要了解 Claim,必須先了解 Claim、ClaimsIdentity 跟 ClaimsPrincipal 是什麼。
Claim 就是關於使用者的一些資訊,Claim Type 跟 Claim Value(可以不用給)就組成一個 Claim,Claim 可以是 姓名、電話、角色、Email 甚至是 角色 等等。Authorization 就是用 Claim 判斷使用者有無授權。
new System.Security.Claims.Claim(ClaimTypes.Role, role.Name)
ClaimsIdentity 則是多個 Claim 的集合,像是 駕照 上面記錄了 姓名、生日、電話,駕照 就是一個 ClaimsIdentity。
ClaimsPrincipal 是多個 ClaimsIdentity 的集合,台灣人都有身分證跟健保卡,有些人還有駕照,身分證、健保卡跟駕照都是 ClaimsIdentity,持有它們的人就是 ClaimsPrincipal。
而每一個 HTTP request 都會產生 HttpContext 物件,該物件就存有目前 request 的資訊,其下可以找到一個類型為 ClaimsPrincipal 的 Property 名為 User,這個 Property 就是由 UseAuthentication() 引入的 Authentication Middleware 產生的。
登入機制可以用 Cookie 或是 JWT 實現,但 Authentication Middleware 怎麼知道要用哪個方法產生 User Property?那就要看 Authentication Scheme 跟 Authentication Handlers。
Authentication Handlers
Authentication Handlers 就是處理驗證的方式,ASP.NET Core Identity 可以呼叫 AuthenticateAsync() API 去驗證使用者已登入,如驗證失敗就呼叫 ChallengeAsync() 將使用者導回 登入頁面,如授權失敗則用 ForbidAsync() 禁止使用者存取,當然也可以自己實現這些行為。下面例子中用了 JWT 跟 Cookie 的驗證方式,如果用了前者,就必須驗證 JWT token 並產生 ClaimsPrincipal 回傳到 HttpContext.User 中;使用後者則會檢查當前 request 的 cookie 並產生 ClaimsPrincipal。
builder.Services.AddAuthentication()
.AddJwtBearer()
.AddCookie();
Authentication Scheme
用了任何一種方式註冊 Authentication Handlers 就稱為 Authentication Scheme,每個 Authentication Scheme 都有一個獨特的名字以識別,且可以自己設定 Authentication Handlers,下面的程式結果跟上面會是一樣,因為它們都有預設的 Scheme Name。
builder.Services.AddAuthentication()
.AddJwtBearer("Bearer")
.AddCookie("Cookies");
Blazor Authentication
Blazor 用的驗證方式跟 ASP.NET Core 一樣,不過 Blazor WebAssembly 跟 Blazor Server 又有不同,前者的驗證就像任何前端網站一樣可以被繞過,因為使用者一端的程式可以被使用者改動,因此發送資料的 API 端一定也需要驗證;後者則可用內建的 AuthenticationStateProvider 取得前面說的 HttpContext.User,筆者此前就是自己繼承並重寫這項 Service 實現 JWT 驗證的。
AuthenticationStateProvider 就是昨天說到的 <AuthorizeView> 及 <CascadingAuthenticationState> 可以取得當前驗證狀態的原因,但如果沒有要重寫預設驗證機制的話,建議不要自己在 Component 注入一個 AuthenticationStateProvider 出來,直接使用的缺點很明顯,若當前 request 的驗證狀態有改動,因為你改動了這個 Component 的驗證機制,該 Component 就不會被告知。
可以看到下圖,ApiAuthenticationStateProvider 繼承了 AuthenticationStateProvider,並重寫了 Task<AuthenticationState>,這個 Property 可以取得當前的驗證狀態,MarkUserAsAuthenticated() 跟 MarkUserAsLoggedOut() 則是筆者自己寫的方法用以標示使用者通過驗證及登出系統,NotifyAuthenticationStateChanged() 顧名思義會通知各個 Component 當前驗證狀態,這就是自己繼承並重寫的案例。

下圖則是在 Component 取得當前 request 的 HttpContext.User 及 Claims 的作法,不過這裡是先利用服務取得 User 再取得其下 Claims,其實是多此一舉了。

如果只是要取得 HttpContext.User,只要如下圖般就可以了,因為 Task<AuthenticationState> 會以 [CascadingParameter] 的方式層層傳遞下去。

引用:
註:本文程式碼透過 .NET 6 + Visual Studio 2022 重構,可點選原文連結與重構後程式碼比較學習,謝謝閱讀,支援原作者