一张大图了解ASP.NET Core 3.1 中的Authentication与Authorization
下面是一張ASP.NET Core 3.1 中關于Authentication與Authorization的主流程框線圖,點擊這里查看全圖:https://johnnyqian.net/images/202004/aspnet-core-3.1-request-processing-pipeline.png
重要組件
一些重要的組件及其源碼鏈接如下:
Authentication
| AuthenticationMiddleware | src/Security/Authentication/Core/src/AuthenticationMiddleware.cs |
| AuthenticationHandler | src/Security/Authentication/Core/src/AuthenticationHandler.cs |
| ????JwtBearerHandler | src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs |
| ????RemoteAuthenticationHandler | src/Security/Authentication/Core/src/RemoteAuthenticationHandler.cs |
| ????????OAuthHandler | src/Security/Authentication/OAuth/src/OAuthHandler.cs |
| ????????????MicrosoftAccountHandler | src/Security/Authentication/MicrosoftAccount/src/MicrosoftAccountHandler.cs |
| ????????????GoogleHandler | src/Security/Authentication/Google/src/GoogleHandler.cs |
| ????????????FacebookHandler | src/Security/Authentication/Facebook/src/FacebookHandler.cs |
| AuthenticationHandlerProvider | src/Http/Authentication.Core/src/AuthenticationHandlerProvider.cs |
| AuthenticationService | src/Http/Authentication.Core/src/AuthenticationService.cs |
Authorization
| AuthorizationMiddleware | src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs |
| AuthorizationHandler | src/Security/Authorization/Core/src/AuthorizationHandler.cs |
| DefaultAuthorizationHandlerProvider | src/Security/Authorization/Core/src/DefaultAuthorizationHandlerProvider.cs |
| DefaultAuthorizationService | src/Security/Authorization/Core/src/DefaultAuthorizationService.cs |
Middleware與這些Components之間通過一些擴展方法鏈接起來:
src/Http/Authentication.Abstractions/src/AuthenticationHttpContextExtensions.cs
?src/Security/Authentication/Core/src/AuthAppBuilderExtensions.cs
src/Security/Authentication/Core/src/AuthenticationServiceCollectionExtensions.cs
src/Http/Authentication.Core/src/AuthenticationCoreServiceCollectionExtensions.cs
?src/Security/Authorization/Policy/src/AuthorizationAppBuilderExtensions.cs
src/Security/Authorization/Policy/src/PolicyServiceCollectionExtensions.cs
src/Security/Authorization/Core/src/AuthorizationServiceCollectionExtensions.cs
幾點分析
與Authentication和Authorization相關的組件基本是對稱存在的,有幾個命名不一致。
與AuthenticationHandler相關的具體實現有很多,這是因為認證的邏輯是通用的,可以由外部的身份提供者來完成。
從AuthorizationMiddleware的路徑可以看出,ASP.NET Core中的Authorization是基于Policy的。
IAuthenticationHandlerProvider中的接口GetHandlerAsync,它是根據authenticationScheme來獲取某一個Handler,只要這個Handler認證成功則整個認證流程完成;相對應的IAuthorizationHandlerProvider中的接口GetHandlersAsync則是獲取一批Handlers(注意是復數)來共同決定授權的結果。
個人認為有一些Components放置的目錄不合理,例如AuthenticationHandlerProvider,AuthenticationService,AuthenticationCoreServiceCollectionExtensions。
基于安全的考慮,ASP.NET Core 不再內置支持Basic Authentication,開發者可以自行編寫相應的AuthenticationHandler。
這兩個Middleware都是面向接口開發的,借助于DI,開發者可以改變Pipeline的處理邏輯。
幾個問題
Authentication的結果存放在哪里?
Authentication的結果由AuthenticationMiddleware中的如下代碼片段確定:
var defaultAuthenticate = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (defaultAuthenticate != null) {var result = await context.AuthenticateAsync(defaultAuthenticate.Name);if (result?.Principal != null){context.User = result.Principal;} }await _next(context); 這段代碼先獲取默認的認證Scheme,接著調用HttpContext的擴展方法AuthenticateAsync(該方法實際由IAuthenticationService來提供)來獲取AuthenticateResult,然后將認證結果中的Principal賦給HttpContext的User屬性,最后將請求傳遞給下一個Middleware(通常是AuthorizationMiddleware)。
從上面的代碼段可以看出,在一個注冊了AuthenticationMiddleware和AuthorizationMiddleware的ASP.NET Core項目中,即使Authentication過程失敗了(即context.User為null),AuthorizationMiddleware也是會運行的。
如何判斷Authorization的成功與否?
同樣的,Authorization的成功與否是由AuthorizationMiddleware來確定的,相關的代碼片段如下:
var authorizeResult =await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: endpoint);if (authorizeResult.Challenged) {if (policy.AuthenticationSchemes.Count > 0){foreach (var scheme in policy.AuthenticationSchemes){await context.ChallengeAsync(scheme);}}else{await context.ChallengeAsync();}return; } else if (authorizeResult.Forbidden) {if (policy.AuthenticationSchemes.Count > 0){foreach (var scheme in policy.AuthenticationSchemes){await context.ForbidAsync(scheme);}}else{await context.ForbidAsync();}return; }await _next(context); 這段代碼先調用policyEvaluator的AuthorizeAsync方法(該方法會調用IAuthorizationService的AuthorizeAsync方法)來獲取PolicyAuthorizationResult:
1 2 3 4 5 6 7 8 9 10 var result = await _authorization.AuthorizeAsync(context.User, resource, policy); if (result.Succeeded) {return PolicyAuthorizationResult.Success(); }// If authentication was successful, return forbidden, otherwise challenge return (authenticationResult.Succeeded)? PolicyAuthorizationResult.Forbid(): PolicyAuthorizationResult.Challenge(); 需要注意的是,在Authorization的流程中是需要借助于Authentication流程的結果以便確定在Authorization失敗后是否返回Challenge(401)還是Forbid(403)。
然后這段代碼根據PolicyAuthorizationResult來調用HttpContext的擴展方法ChallengeAsync或ForbidAsync,并終止整個請求處理流程;或者將請求傳遞給下一個Middleware(通常是EndpointRoutingMiddleware,也即是我們常說的MVC Middleware)。HttpContext的擴展方法ChallengeAsync和ForbidAsync是由IAuthenticationService來提供的。
我們再深入到DefaultAuthorizationService中來了解下多個AuthorizationHandler是如何一起工作的:
var authContext = _contextFactory.CreateContext(requirements, user, resource); var handlers = await _handlers.GetHandlersAsync(authContext); foreach (var handler in handlers) {await handler.HandleAsync(authContext);if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed){break;} }var result = _evaluator.Evaluate(authContext); 從上述代碼可以看到,在InvokeHandlersAfterFailure為true的情況下(默認為true),所有注冊了的AuthorizationHandler都會被執行。接著,代碼調用IAuthorizationEvaluator中的Evaluate方法對整個授權流程進行評估,本質上是檢查AuthorizationHandlerContext中的HasSucceeded屬性,其代碼如下:
/// <summary> /// Flag indicating whether the current authorization processing has succeeded. /// </summary> public virtual bool HasSucceeded {get{return !_failCalled && _succeedCalled && !PendingRequirements.Any();} } 可以看到,授權成功的條件是:
基本原則是,AuthorizationHandler一般不需要顯式調用context.Fail,除非開發者認為某一個requirement必須被當前的AuthorizationHandler滿足。針對同一個requirement,其它的AuthorizationHandler可能會依據某些條件認為該requirement是滿足的。
這里有個問題是,如果某一個AuthorizationHandler顯式調用了context.Fail,那么整個授權流程的結果就是失敗的。那么此時為什么還要繼續執行其它的AuthorizationHandler(InvokeHandlersAfterFailure默認為true),而不是快速失敗(fail-fast)?官方文檔給出的解釋如下:
If a handler calls context.Succeed or context.Fail, all other handlers are still called.?This allows requirements to produce side effects, such as logging, which takes place even if another handler has successfully validated or failed a requirement. When set to false, the InvokeHandlersAfterFailure property (available in ASP.NET Core 1.1 and later) short-circuits the execution of handlers when context.Fail is called. InvokeHandlersAfterFailure defaults to true, in which case all handlers are called.
沒有任何一個AuthorizationHandler顯式調用了context.Fail
至少有一個AuthorizationHandler顯式調用了context.Succeed
PendingRequirements這個集合為空,也即是所有的requirements都被滿足了
[AllowAnonymous]屬性是如何工作的?
[AllowAnonymous]屬性可以繞開(bypass)整個授權流程,即使相應的Controller或者Action上有[Authorize]屬性。這也是由AuthorizationMiddleware中的一段代碼來實現的:
// Allow Anonymous skips all authorization if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null) {await _next(context);return; } 參考文章
ASP.NET Core Middleware
ASP.NET Core authentication
ASP.NET Core authorization
Policy-based authorization in ASP.NET Core
ASP.NET Core - Middleware
ASP.NET Core in Action - What is middleware?
ASP.NET Core middleware and authorization
ASP.NET Core 中的那些認證中間件及一些重要知識點
ASP.NET Core 中的管道機制
總結
以上是生活随笔為你收集整理的一张大图了解ASP.NET Core 3.1 中的Authentication与Authorization的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分享一些支持多租户的开源框架
- 下一篇: .Net Core微服务架构技术栈的那些