前言
在讀這篇文章之間,建議先看一下我的 ASP.NET Core 之 Identity 入門系列(一,二,三)奠定一下基礎。
有關于 Authentication 的知識太廣,所以本篇介紹幾個在 ASP.NET Core 認證中會使用到的中間件,還有Authentication的一些零碎知識點,這些知識點對于 ASP.NET 認證體系的理解至關重要。
在 Github 中 ASP.NET Core 關于 Authentication 的實現有以下幾個包,那么這幾個包的功能分別是干什么用的呢?我們一一看一下。
- 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 中,對于 Authentication(認證) 的抽象實現,此中間件用來處理或者提供管道中的 HttpContext 里面的 AuthenticationManager 相關功能抽象實現。
HttpContext 中的 User 相關信息同樣在此中間件中被初始化。
對于開發人員只需要了解此中間件中的這幾個對象即可:
AuthenticationOptions
對象主要是用來提供認證相關中間件配置項,后面的 OpenIdConnect,OAuth,Cookie 等均是繼承于此。AuthenticationHandler
對請求流程中(Pre-Request)中相關認證部分提供的一個抽象處理類,同樣后面的其他幾個中間件均是繼承于此。
在 AuthenticationHandler
中, 有幾個比較重要的方法:
HandleAuthenticateAsync
:處理認證流程中的一個核心方法,這個方法返回AuthenticateResult
來標記是否認證成功以及返回認證過后的票據(AuthenticationTicket)。HandleUnauthorizedAsync
:可以重寫此方法用來處理相應401未授權等問題,修改頭,或者跳轉等。HandleSignInAsync
:對齊 HttpContext AuthenticationManager 中的 SignInAsyncHandleSignOutAsync
:對齊 HttpContext AuthenticationManager 中的 SignOutAsyncHandleForbiddenAsync
:對齊 HttpContext AuthenticationManager 中的 ForbidAsync,用來處理禁用等結果
以上關于 AuthenticationHandler 我列出來的這些方法都是非常容易理解的,我們在繼承Authentication實現我們自己的一個中間件的時候只需要重寫上面的一個或者多個方法即可。
還有一個 RemoteAuthenticationHandler
它也是繼承AuthenticationHandler,主要是針對于遠程調用提供出來的一個抽象,什么意思呢?因為很多時候我們的認證是基于OAuth的,也就是說用戶的狀態信息是存儲到Http Header 里面每次進行往來的,而不是cookie等,所以在這個類里面了一個HandleRemoteAuthenticateAsync
的函數。
Microsoft.AspNetCore.Authentication.Cookies
Cookies 認證是 ASP.NET Core Identity 默認使用的身份認證方式,那么這個中間件主要是干什么的呢
我們知道,在 ASP.NET Core 中已經沒有了 Forms 認證,取而代之的是一個叫 “個人用戶賬戶” 的一個東西,如下圖,你在新建一個ASP.ENT Core Web 應用程序的時候就會發現它,它實際上就是 Identity。那么Forms認證去哪里了呢?對,就是換了個名字叫 Identity。
在此中間件中,主要是針對于Forms認證的一個實現,也就是說它通過Cookie把用戶的個人身份信息通過加密的票據存儲的Cookie中去,如果看過我之前Identity系列文章的話,那么應該知道用戶的個人身份信息就是 ClaimsIdentity 相關的東西。
這個中間件引用了Authentication,CookieAuthenticationHandler 類繼承了 AuthenticationHandler 并重寫了 HandleAuthenticateAsync
,HandleSignInAsync
,HandleSignOutAsync
,HandleForbiddenAsync
,HandleUnauthorizedAsync
等方法,就是上一節中列出來的幾個方法。
我們主要看一下核心方法 HandleAuthenticateAsync
在 Cookie 中間件怎么實現的:
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));
}
總結一下就是解密Http請求中的Cookie信息,然后驗證Cookie是否合法,然后提取Cookie中的信息返回結果。
還有一個方法就是 HandleSignInAsync
,根據名字可以看出主要是處理登入相關操作的,在這個方法里面主要是根據Claims信息生成加入過后的票據,同時會向票據中寫入過期時間,是否持久化等信息。 是否持久化的意思就是用戶在登陸界面是否勾選了 “記住我” 這個操作。
Microsoft.AspNetCore.Authentication.OAuth
OAuth 是針對于 OAuth 2.0 標準實現的一個客戶端程序,記住是客戶端,它不具備發放Token或者 Client_id ,Code 等的功能,它的作用是幫你簡化對OAuth2.0服務端程序的調用。 它對應 OAuth 2.0 標準中的 “獲取Access_Token” 這一步驟,如果對騰訊開放平臺QQ授權比較了解的話,就是對應 “使用Authorization_Code獲取Access_Token” 這一步驟。
點擊 這里 查看圖片詳情。
OAuth 實現的具體細節就不一一介紹了。
Microsoft.AspNetCore.Authentication.OpenIdConnect
獲取 OpenId 是OAuth 授權中的一個步驟,OpenId 它是具體的一個Token Key,不要把他理解成一種授權方式或者和OAuth不同的另外一種東西,他們是一體的。
代碼上就不詳細說了,和上面的都差不多。主要說一下它們之間的區別或者叫聯系。
OAuth 它主要是針對于授權(Authorization),而OpenID主要是針對于認證(Authentication),他們之間是互補的。
那什么叫授權呢? 比如小明是使用我們網站的一個用戶,他現在要在另外一個網站使用在我們網站注冊的賬號,那授權就是代表小明在另外一個網站能夠做什么東西? 比如能夠查看資料,頭像,相冊等等,授權會給用戶發放一個叫 Access_Token 的東西。
而認證關注的這個用戶是誰,它是用來證明用戶東西。比如小明要訪問它的相冊,那我們網站就需要小明提供一個叫OpenId的一個東西,我們只認這個OpenId。那小明從哪里得到它的這個OpenId呢,對,就是使用上一步的Access_Token 來換取這 個 OpenId ,以后訪問的時候不認 Access_Token ,只認識OpenId這個東西。
一般情況下,OpenId 是需要客戶端進行持久化的,那么對應在 ASP.NET Core Identity 中,就是存儲在 UsersLogin 表里面的 ProviderKey 字段,懂了吧,懂了給個推薦唄
Microsoft.AspNetCore.Authentication.JwtBearer
JwtBearer 這個中間件是依賴于上一步的 OpenIdConnect 中間件的,看到了吧,其實這幾個中間件是環環嵌套的。
可能很多同學聽說過 Jwt,但是大多數人都有一個誤區,認為JWT是一種認證方式,經常在QQ群里面聽過 前面一個同學在問 實際開發中前后端分離的時候安全怎么做的?,下面一個人回答使用JWT。
其實JWT 它不是一種認證方式,也不是一種認證的技術,它是一個規范,一個標準。
Jwt(Json Web Token)的官網是 https://jwt.io,下面是對JWT的一個說明
JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties.
JSON Web Tokens(JWT) 是一個開放的行業標準( RFC 7519),用于在雙方之間傳遞安全的Claims。
JWT 在身份認證中的應用場景:
在身份認證場景下,一旦用戶完成了登陸,在接下來的每個請求中包含JWT,可以用來驗證用戶身份以及對路由,服務和資源的訪問權限進行驗證。由于它的開銷非常小,可以輕松的在不同域名的系統中傳遞,所有目前在單點登錄(SSO)中比較廣泛的使用了該技術。
好了,不過多的說了。 我們還是接著看一下 JwtBearer 中間件,同樣它重寫了 HandleAuthenticateAsync
方法。
大致步驟如下:
- 讀取 Http Request Header 中的 Authorization 信息
- 讀取 Authorization 值里面的 Bearer 信息
- 驗證 Bearer 是否合法,會得到一個 ClaimsPrincipal
- 使用 ClaimsPrincipal 構建一個 Ticket(票據)
- 調用 Options.Events.TokenValidated(context),用戶可以重寫此方法驗證Token合法性
- 返回驗證成功
其他的知識點
這幾個中間件對會有對應的 Options 配置項,在這些配置項中,都會有 AuthenticationScheme
, AutomaticAuthenticate
, AutomaticChallenge
這幾個屬性,那這幾個東西都是干嘛的呢?
AuthenticationScheme
我在 《ASP.NET Core 之 Identity 入門(二)》 一文中提到過這個知識點,當時說很重要,這里可以看到了吧,每一種驗證中間件都會使用到這個東西,我比較偏向于把這個翻譯成 “認證方案”。
我們知道,在 MVC 程序中一般通過在 Controller 或者 Action 上 打標記(Attribute)的方式進行授權,最典型的就是新建一個項目的時候里面的AccountController。
[Authorize]
public class AccountController : Controller
{
}
在 Authorize 這個 Attribute 中,有一個屬性叫做 ActiveAuthenticationSchemes 的東西,那么這個東西是干什么用的呢?
ActiveAuthenticationSchemes 就是對應著中間件Options里面配置的 AuthenticationScheme ,如果你不指定的話,在使用多個身份驗證組件的時候會有問題,會有什么問題呢?往下看
AutomaticAuthenticate
AutomaticAuthenticate 很簡單,是一個bool類型的字段,用來配置是否處理 AuthenticationHandler 是否處理請求。或者你可以理解為中間件是不是自動的處理認證相關的業務。
AutomaticChallenge
這個重要哦! 當我們使用多個身份驗證中間件的時候,那么就要用到這個配置項了,該配置項是用來設置哪個中間件會是身份驗證流程中的默認中間件,當代碼運行到 Controller 或者 Action 上的 [Authorize]
這個標記的時候,就會觸發身份驗證流程。默認情況下MVC的Filter會自動的觸發[Authorize]
,當然也有一種手動觸發Authorize的辦法就是使用HttpContext.Authentication.ChallengeAsync()
。
實際上,在驗證中間件的管道流程中,應該只有一個組件被設定為 AutomaticChallenge = true
,但其實大多數的中間件這個參數默認都是 true ,這些中間件包括(Identity, Cookie, OAuth, OpenId, IISIntegration, WebListener)等, 這就導致了在整個驗證流程中會觸發多個中間件對其進行相應,這種沖突大部分不是用戶期望的結果。
不幸的是,目前框架對于這種情況并沒有一個健壯的機制,如果開發人員對于這種機制不是很清楚的話,可能會造成很大的困擾。
幸運的是,ASP.NET Core 團隊已經意識到了這個問題,他們將在 NET Standard 2.0 中對此重新進行設計,比如手動觸發的時候應該怎么處理,有多個的時候怎么處理,以及會添加一些語法糖。
目前情況下,當有多個驗證中間件的時候,應該怎么處理呢?比如同時使用 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()
不受 AuthorizationPolicie 影響。
總結
本篇介紹了 ASP.NET Core 有關 Authentication 的幾個中間件,然后還有幾個比較重要的知識點,這篇文章內容有點多,對于一些人來說可能需要一點時間消化。
最后,如果你覺得這篇文章對你有幫助的話,謝謝你的【推薦】。
如果你對 .NET Core 感興趣可以關注我,我會定期在博客分享關于 .NET Core 的學習心得。
本文地址:http://www.cnblogs.com/savorboard/p/aspnetcore-authentication.html
作者博客:Savorboard
歡迎轉載,請在明顯位置給出出處及鏈接
文章列表
留言列表