asp.net core 認證和授權實例詳解
目錄
- 正文
- 認證是什么?
- 授權是什么?
- 認證和授權的關系?
正文
使用asp.net core 開發應用系統過程中,基本上都會涉及到用戶身份的認證,及授權訪問控制,因此了解認證和授權流程也相當重要,下面通過分析asp.net core 框架中的認證和授權的源碼來分析認證、授權的原理及認證和授權的關系。
認證是什么?
認證是應用系統識別當前訪問者的身份的一個過程,當應用系統接收到瀏覽器的請求后,通常會根據請求中攜帶的一些用戶的的關鍵信息來識別當前登錄用戶的身份,通過解析這些信息,對用戶進行合法性校驗并進行解密,如果校驗通過,則表示認證通過,應用系統會將認證通過后的用戶信息存儲到Http請求上下文中,以便后續業務使用及授權流程中使用。
asp.net core中通常將認證信息加密后存儲到cookie中,每次訪問需要認證的頁面時將這些cookie信息發送到應用系統,以便應用系統識別訪問者的身份,也就是經典的Cookie認證。
需要注意的是:認證僅僅只是識別當前訪問用戶的身份,并不負責具體的訪問權限控制邏輯,如不具備某個資源的訪問權限返回403,未登錄返回401等,這些均由授權流程來控制。
asp.net core 中負責認證流程的中間件是AuthenticationMiddleware 類,以下是asp.net core 3.1 的源代碼,可以看到,先遍歷所有實現了IAuthenticationRequestHandler接口的認證方案,并調用IAuthenticationRequestHandler接口的HandleRequestAsync方法,如果認證通過,則不再繼續往下執行,并且此時HttpContext.User已經包含認證后的用戶信息,如果所有實現 IAuthenticationRequestHandler 接口的認證方案,都未能對當前訪問用戶進行身份認證,則使用默認的認證方案進行認證(也就是:GetDefaultAuthenticateSchemeAsync返回的認證方案),可以看到認證流程即使沒能識別當前訪問者的用戶身份,也會繼續執行下一個流程,(尾部:await _next(context);)
public class AuthenticationMiddleware { private readonly RequestDelegate _next; public IAuthenticationSchemeProvider Schemes { get; set; } public AuthenticationMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes) { if (next == null) { throw new ArgumentNullException("next"); } if (schemes == null) { throw new ArgumentNullException("schemes"); } _next = next; Schemes = schemes; } public async Task Invoke(HttpContext context) { context.Features.Set((IAuthenticationFeature)new AuthenticationFeature { OriginalPath = context.Request.Path, OriginalPathBase = context.Request.PathBase }); IAuthenticationHandlerProvider handlers = context.RequestServices.GetRequiredService<IAuthenticationHandlerProvider>(); foreach (AuthenticationScheme item in await Schemes.GetRequestHandlerSchemesAsync()) { IAuthenticationRequestHandler authenticationRequestHandler = (await handlers.GetHandlerAsync(context, item.Name)) as IAuthenticationRequestHandler; bool flag = authenticationRequestHandler != null; if (flag) { flag = await authenticationRequestHandler.HandleRequestAsync(); } if (flag) { return; } } AuthenticationScheme authenticationScheme = await Schemes.GetDefaultAuthenticateSchemeAsync(); if (authenticationScheme != null) { //內部調用IAuthenticationService進行認證。 AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticationScheme.Name); if (authenticateResult?.Principal != null) { context.User = authenticateResult.Principal; } } await _next(context); } }
授權是什么?
授權是確定當前訪問用戶是否具備訪問某個系統資源權限的過程,對于需要授權才能訪問的系統資源,通常通過[Authorize]特性來標識,通過該特性,可以指定該資源需要哪個用戶角色才能訪問、必須符合哪個授權策略才能訪問,以及訪問該資源時采用的用戶認證方案是什么,當用戶訪問系統的某個API或者頁面時,授權流程會檢查當前用戶是否具備該API或者頁面的訪問權限,如果授權檢查失敗,那么會判斷當前用戶是否已經認證通過,如果認證通過,但無訪問該資源的權限,那么返回403(禁止訪問),如果未認證,那么直接返回401(未認證),表示需要用戶登錄認證后在進行訪問,需要注意的是:檢查是否具備訪問權限之前會先進行用戶身份的認證,至于用什么認證方案就看AuthorizeAttribute有沒有指定特定的認證方案,如果沒有,則直接采用認證流程的認證成功的身份信息。
asp.net core 中,授權流程的執行是通過AuthorizationMiddleware類來完成的,以下是asp.net core 3.1中的源碼。
// Microsoft.AspNetCore.Authorization.AuthorizationMiddleware using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; public class AuthorizationMiddleware { private const string AuthorizationMiddlewareInvokedWithEndpointKey = "__AuthorizationMiddlewareWithEndpointInvoked"; private static readonly object AuthorizationMiddlewareWithEndpointInvokedValue = new object(); private readonly RequestDelegate _next; private readonly IAuthorizationPolicyProvider _policyProvider; public AuthorizationMiddleware(RequestDelegate next, IAuthorizationPolicyProvider policyProvider) { _next = next ?? throw new ArgumentNullException("next"); _policyProvider = policyProvider ?? throw new ArgumentNullException("policyProvider"); } public async Task Invoke(HttpContext context) { if (context == null) { throw new ArgumentNullException("context"); } Endpoint endpoint = context.GetEndpoint(); if (endpoint != null) { context.Items["__AuthorizationMiddlewareWithEndpointInvoked"] = AuthorizationMiddlewareWithEndpointInvokedValue; } //獲取訪問當前資源所需要的所有角色權限,及授權策略,以及訪問該資源時需要使用的認證方案列表,并統一合并到一個AuthorizationPolicy對象中。 IReadOnlyList<IAuthorizeData> authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>(); AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData); if (policy == null) { await _next(context); return; } IPolicyEvaluator policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>(); //通過IPolicyEvaluator.AuthenticateAsync()方法,對當前訪問者進行認證,至于使用哪種方案認證,根據該資源要求使用的認證方案來,如果沒有指定, //則使用默認認證方案進行認證。 AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context); //如果包含實現了IAllowAnonymous接口的特性,則不進行授權檢查。 if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null) { await _next(context); return; } //這里調用AuthorizeAsync進行授權檢查,注意,這里將上一步認證結果authenticationResult也傳到了授權檢查方法內部。 PolicyAuthorizationResult policyAuthorizationResult = await policyEvaluator.AuthorizeAsync(policy, authenticationResult, context, endpoint); //檢查授權結果,如果是未登錄,則返回401未認證,讓用戶進行登錄,如果該資源指定了特定的認證方案,則調用特定認證方案的Challenge方法, //否則調用默認認證方案的Challenge方法,通常Challenge做的事情就是重定向用戶的瀏覽器到登錄頁面或者對于ajax異步請求返回401. if (policyAuthorizationResult.Challenged) { if (policy.AuthenticationSchemes.Any()) { foreach (string authenticationScheme in policy.AuthenticationSchemes) { await context.ChallengeAsync(authenticationScheme); } } else { await context.ChallengeAsync(); } } //如果當前訪問者用戶身份認證通過,但是不被允許訪問該資源的權限,那么默認返回401(禁止訪問)給瀏覽器端,通常對于未授權的訪問請求,應用常常的做法是將用戶的瀏覽器重定向到禁止訪問的提示頁面,或者對于ajax異步請求來說,通常返回403狀態碼,和上面未認證情況一樣,如果該資源指定了特定的認證方案,那么會調用特定認證方案的Forbid方法,否則調用默認認證方案的Forbid方法。 else if (policyAuthorizationResult.Forbidden) { if (policy.AuthenticationSchemes.Any()) { foreach (string authenticationScheme2 in policy.AuthenticationSchemes) { await context.ForbidAsync(authenticationScheme2); } } else { await context.ForbidAsync(); } } else { await _next(context); } } }
IPolicyEvaluator接口實現類 PolicyEvaluator類代碼如下,該類主要是負責授權流程中的認證和授權。
// Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator using System; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Internal; public class PolicyEvaluator : IPolicyEvaluator { private readonly IAuthorizationService _authorization; public PolicyEvaluator(IAuthorizationService authorization) { _authorization = authorization; } public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) { //這里去判斷當前資源是否有要求特定的認證方案進行認證,如果有指定特定的認證方案,則分別對每個認證方案進行認證,并把認證后的用戶信息進行合并 //最終存儲到HttpContext.User屬性中,并返回認證成功,如果沒有指定認證方案,則使用認證流程中已經認證的用戶信息作為認證結果返回, //從這里可以看出,認證流程還是很有必要的,在資源沒有指定認證方案的前提下,認證流程為授權流程提供當前訪問者的身份信息,以便執行是否具備相應資源的訪問權限檢查,否則就直接進入Challenge流程將要求用戶先進行身份認證了 if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0) { ClaimsPrincipal newPrincipal = null; foreach (string authenticationScheme in policy.AuthenticationSchemes) { AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticationScheme); if (authenticateResult != null && authenticateResult.Succeeded) { newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, authenticateResult.Principal); } } if (newPrincipal != null) { context.User = newPrincipal; return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes))); } context.User = new ClaimsPrincipal(new ClaimsIdentity()); return AuthenticateResult.NoResult(); } return (context.User?.Identity?.IsAuthenticated).GetValueOrDefault() ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) : AuthenticateResult.NoResult(); } //resource為EndPoint對象。 public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource) { if (policy == null) { throw new ArgumentNullException("policy"); } //這里調用IAuthorizationService.AuthorizeAsync方法進行授權檢查,默認實現類為:DefaultAuthorizationService。 if ((await _authorization.AuthorizeAsync(context.User, resource, policy)).Succeeded) { return PolicyAuthorizationResult.Success(); } //下面這句表示如果授權檢查失敗的情況下是進入Forbid流程還是進入Challenge流程,可以看到如果認證成功,那么表示無權限訪問進入Forbid流程。 //如果未認證,則進入Challenge流程,引導用戶登錄認證。 return authenticationResult.Succeeded ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge(); } }
認證和授權的關系?
授權檢查之前都會先執行用戶身份的認證,不過這里的認證流程只有在被訪問的資源有指定特定的認證方案時才會執行,否則直接采用統一認證流程中的產生的認證信息。
可以理解為認證流程一方面是為了告訴應用系統當前訪問者的身份,一方面是為了給授權檢查時識別用戶的身份信息,當資源沒有指定采用何種認證方案時,授權流程將會采用統一認證流程里認證通過產生的用戶信息,如果不啟用認證流程,并且被訪問的資源也沒有指定特定的認證方案對訪問者身份進行認證時,那么最終訪問該資源時還是會被要求先登錄認證,因此認證流程的另外一個用途就是為授權流程提供默認的用戶認證信息。
總結起來說,
認證流程主要有如下幾個作用:
- 識別系統訪問者的身份信息,認證通過后提供給后續業務使用。
- 給授權流程提供訪問者身份信息(資源沒有指定特定認證方案時,采用默認認證方案認證通過的用戶信息)。
- 實現授權失敗后的處理邏輯,比如授權檢查失敗后返回的 401(未認證),403(禁止訪問)等最終都是認證方案的 ChallegeAsync方法以及ForbidAsync方法來處理,這些方法是IAuthenticationHandler里面定義的,這些流程在授權失敗為401/403的時候分別被授權流程調用。
授權流程主要如下幾個作用:
- 授權流程主要是檢查當前用戶是否具備指定資源的訪問權限,如果授權檢查失敗,如401(未認證),403(禁止訪問),那么最終會分別調用認證方案的ChallegenAsync和ForbidAsync方法,也就是說,授權流程側重于授權失敗后的流程控制。
- 授權流程另外一個主要的任務是檢查授權策略是否均能檢驗通過,如果一個資源通過AuthorizeAttribute的Policy屬性指定了一個或者多個授權策略,那么必須所有授權策略都驗證通過才算授權成功,如果未指定授權策略,那么就驗證默認的授權策略是否能檢驗通過,默認的授權策略則是要求必須用戶認證通過才允許訪問資源。
授權流程本質上就是遍歷所有注入到容器中的IAuthorizationHandler(微軟默認在AddAuthorization的時候向容器注入了:PassThroughAuthorizationHandler,這個授權處理程序遍歷AuthorizationHandlerContext.Requirements中所有實現了IAuthorizationHandler的Requirement類,并調用其HandleAsync方法來檢查當前Requirement是否能校驗通過),并對訪問指定資源所要滿足的所有策略中包含的Requirement進行驗證,如果所有策略包含的Requirement都驗證通過,那么表示授權成功,這里的Requirement是指實現了IAuthorizationRequirement的類,這個接口是一個空接口,用于標記Requirement使用。
以上就是asp.net core 認證和授權實例詳解的詳細內容,更多關于asp.net core 認證授權的資料請關注其它相關文章!
相關文章:
1. ASP.NET Core 5.0中的Host.CreateDefaultBuilder執行過程解析2. Asp.net Core項目配置HTTPS支持3. 在Asp.net core項目中使用WebSocket4. ASP.NET Core WebSocket集群實現思路詳解5. 關于Jenkins + Docker + ASP.NET Core自動化部署的問題(避免踩坑)6. asp.net core 中的Jwt(Json Web Token)的使用詳解7. ASP.NET Core依賴注入DI容器的方法實現8. asp.net core服務限制堆內存大小的操作方法9. ASP.NET Core整合Zipkin鏈路跟蹤的實現方法10. ASP.NET Core自定義中間件的方式詳解
