asp.net core 認(rèn)證和授權(quán)實(shí)例詳解
正文
使用asp.net core 開發(fā)應(yīng)用系統(tǒng)過程中,基本上都會(huì)涉及到用戶身份的認(rèn)證,及授權(quán)訪問控制,因此了解認(rèn)證和授權(quán)流程也相當(dāng)重要,下面通過分析asp.net core 框架中的認(rèn)證和授權(quán)的源碼來分析認(rèn)證、授權(quán)的原理及認(rèn)證和授權(quán)的關(guān)系。
認(rèn)證是什么?
認(rèn)證是應(yīng)用系統(tǒng)識別當(dāng)前訪問者的身份的一個(gè)過程,當(dāng)應(yīng)用系統(tǒng)接收到瀏覽器的請求后,通常會(huì)根據(jù)請求中攜帶的一些用戶的的關(guān)鍵信息來識別當(dāng)前登錄用戶的身份,通過解析這些信息,對用戶進(jìn)行合法性校驗(yàn)并進(jìn)行解密,如果校驗(yàn)通過,則表示認(rèn)證通過,應(yīng)用系統(tǒng)會(huì)將認(rèn)證通過后的用戶信息存儲(chǔ)到Http請求上下文中,以便后續(xù)業(yè)務(wù)使用及授權(quán)流程中使用。
asp.net core中通常將認(rèn)證信息加密后存儲(chǔ)到cookie中,每次訪問需要認(rèn)證的頁面時(shí)將這些cookie信息發(fā)送到應(yīng)用系統(tǒng),以便應(yīng)用系統(tǒng)識別訪問者的身份,也就是經(jīng)典的Cookie認(rèn)證。
需要注意的是:認(rèn)證僅僅只是識別當(dāng)前訪問用戶的身份,并不負(fù)責(zé)具體的訪問權(quán)限控制邏輯,如不具備某個(gè)資源的訪問權(quán)限返回403,未登錄返回401等,這些均由授權(quán)流程來控制。
asp.net core 中負(fù)責(zé)認(rèn)證流程的中間件是AuthenticationMiddleware 類,以下是asp.net core 3.1 的源代碼,可以看到,先遍歷所有實(shí)現(xiàn)了IAuthenticationRequestHandler接口的認(rèn)證方案,并調(diào)用IAuthenticationRequestHandler接口的HandleRequestAsync方法,如果認(rèn)證通過,則不再繼續(xù)往下執(zhí)行,并且此時(shí)HttpContext.User已經(jīng)包含認(rèn)證后的用戶信息,如果所有實(shí)現(xiàn) IAuthenticationRequestHandler 接口的認(rèn)證方案,都未能對當(dāng)前訪問用戶進(jìn)行身份認(rèn)證,則使用默認(rèn)的認(rèn)證方案進(jìn)行認(rèn)證(也就是:GetDefaultAuthenticateSchemeAsync返回的認(rèn)證方案),可以看到認(rèn)證流程即使沒能識別當(dāng)前訪問者的用戶身份,也會(huì)繼續(xù)執(zhí)行下一個(gè)流程,(尾部: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) { //內(nèi)部調(diào)用IAuthenticationService進(jìn)行認(rèn)證。 AuthenticateResult authenticateResult = await context.AuthenticateAsync(authenticationScheme.Name); if (authenticateResult?.Principal != null) { context.User = authenticateResult.Principal; } } await _next(context); } }
授權(quán)是什么?
授權(quán)是確定當(dāng)前訪問用戶是否具備訪問某個(gè)系統(tǒng)資源權(quán)限的過程,對于需要授權(quán)才能訪問的系統(tǒng)資源,通常通過[Authorize]特性來標(biāo)識,通過該特性,可以指定該資源需要哪個(gè)用戶角色才能訪問、必須符合哪個(gè)授權(quán)策略才能訪問,以及訪問該資源時(shí)采用的用戶認(rèn)證方案是什么,當(dāng)用戶訪問系統(tǒng)的某個(gè)API或者頁面時(shí),授權(quán)流程會(huì)檢查當(dāng)前用戶是否具備該API或者頁面的訪問權(quán)限,如果授權(quán)檢查失敗,那么會(huì)判斷當(dāng)前用戶是否已經(jīng)認(rèn)證通過,如果認(rèn)證通過,但無訪問該資源的權(quán)限,那么返回403(禁止訪問),如果未認(rèn)證,那么直接返回401(未認(rèn)證),表示需要用戶登錄認(rèn)證后在進(jìn)行訪問,需要注意的是:檢查是否具備訪問權(quán)限之前會(huì)先進(jìn)行用戶身份的認(rèn)證,至于用什么認(rèn)證方案就看AuthorizeAttribute有沒有指定特定的認(rèn)證方案,如果沒有,則直接采用認(rèn)證流程的認(rèn)證成功的身份信息。
asp.net core 中,授權(quán)流程的執(zhí)行是通過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; } //獲取訪問當(dāng)前資源所需要的所有角色權(quán)限,及授權(quán)策略,以及訪問該資源時(shí)需要使用的認(rèn)證方案列表,并統(tǒng)一合并到一個(gè)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()方法,對當(dāng)前訪問者進(jìn)行認(rèn)證,至于使用哪種方案認(rèn)證,根據(jù)該資源要求使用的認(rèn)證方案來,如果沒有指定, //則使用默認(rèn)認(rèn)證方案進(jìn)行認(rèn)證。 AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context); //如果包含實(shí)現(xiàn)了IAllowAnonymous接口的特性,則不進(jìn)行授權(quán)檢查。 if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null) { await _next(context); return; } //這里調(diào)用AuthorizeAsync進(jìn)行授權(quán)檢查,注意,這里將上一步認(rèn)證結(jié)果authenticationResult也傳到了授權(quán)檢查方法內(nèi)部。 PolicyAuthorizationResult policyAuthorizationResult = await policyEvaluator.AuthorizeAsync(policy, authenticationResult, context, endpoint); //檢查授權(quán)結(jié)果,如果是未登錄,則返回401未認(rèn)證,讓用戶進(jìn)行登錄,如果該資源指定了特定的認(rèn)證方案,則調(diào)用特定認(rèn)證方案的Challenge方法, //否則調(diào)用默認(rèn)認(rèn)證方案的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(); } } //如果當(dāng)前訪問者用戶身份認(rèn)證通過,但是不被允許訪問該資源的權(quán)限,那么默認(rèn)返回401(禁止訪問)給瀏覽器端,通常對于未授權(quán)的訪問請求,應(yīng)用常常的做法是將用戶的瀏覽器重定向到禁止訪問的提示頁面,或者對于ajax異步請求來說,通常返回403狀態(tài)碼,和上面未認(rèn)證情況一樣,如果該資源指定了特定的認(rèn)證方案,那么會(huì)調(diào)用特定認(rèn)證方案的Forbid方法,否則調(diào)用默認(rèn)認(rèn)證方案的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接口實(shí)現(xiàn)類 PolicyEvaluator類代碼如下,該類主要是負(fù)責(zé)授權(quán)流程中的認(rèn)證和授權(quán)。
// 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) { //這里去判斷當(dāng)前資源是否有要求特定的認(rèn)證方案進(jìn)行認(rèn)證,如果有指定特定的認(rèn)證方案,則分別對每個(gè)認(rèn)證方案進(jìn)行認(rèn)證,并把認(rèn)證后的用戶信息進(jìn)行合并 //最終存儲(chǔ)到HttpContext.User屬性中,并返回認(rèn)證成功,如果沒有指定認(rèn)證方案,則使用認(rèn)證流程中已經(jīng)認(rèn)證的用戶信息作為認(rèn)證結(jié)果返回, //從這里可以看出,認(rèn)證流程還是很有必要的,在資源沒有指定認(rèn)證方案的前提下,認(rèn)證流程為授權(quán)流程提供當(dāng)前訪問者的身份信息,以便執(zhí)行是否具備相應(yīng)資源的訪問權(quán)限檢查,否則就直接進(jìn)入Challenge流程將要求用戶先進(jìn)行身份認(rèn)證了 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"); } //這里調(diào)用IAuthorizationService.AuthorizeAsync方法進(jìn)行授權(quán)檢查,默認(rèn)實(shí)現(xiàn)類為:DefaultAuthorizationService。 if ((await _authorization.AuthorizeAsync(context.User, resource, policy)).Succeeded) { return PolicyAuthorizationResult.Success(); } //下面這句表示如果授權(quán)檢查失敗的情況下是進(jìn)入Forbid流程還是進(jìn)入Challenge流程,可以看到如果認(rèn)證成功,那么表示無權(quán)限訪問進(jìn)入Forbid流程。 //如果未認(rèn)證,則進(jìn)入Challenge流程,引導(dǎo)用戶登錄認(rèn)證。 return authenticationResult.Succeeded ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge(); } }
認(rèn)證和授權(quán)的關(guān)系?
授權(quán)檢查之前都會(huì)先執(zhí)行用戶身份的認(rèn)證,不過這里的認(rèn)證流程只有在被訪問的資源有指定特定的認(rèn)證方案時(shí)才會(huì)執(zhí)行,否則直接采用統(tǒng)一認(rèn)證流程中的產(chǎn)生的認(rèn)證信息。
可以理解為認(rèn)證流程一方面是為了告訴應(yīng)用系統(tǒng)當(dāng)前訪問者的身份,一方面是為了給授權(quán)檢查時(shí)識別用戶的身份信息,當(dāng)資源沒有指定采用何種認(rèn)證方案時(shí),授權(quán)流程將會(huì)采用統(tǒng)一認(rèn)證流程里認(rèn)證通過產(chǎn)生的用戶信息,如果不啟用認(rèn)證流程,并且被訪問的資源也沒有指定特定的認(rèn)證方案對訪問者身份進(jìn)行認(rèn)證時(shí),那么最終訪問該資源時(shí)還是會(huì)被要求先登錄認(rèn)證,因此認(rèn)證流程的另外一個(gè)用途就是為授權(quán)流程提供默認(rèn)的用戶認(rèn)證信息。
總結(jié)起來說,
認(rèn)證流程主要有如下幾個(gè)作用:
- 識別系統(tǒng)訪問者的身份信息,認(rèn)證通過后提供給后續(xù)業(yè)務(wù)使用。
- 給授權(quán)流程提供訪問者身份信息(資源沒有指定特定認(rèn)證方案時(shí),采用默認(rèn)認(rèn)證方案認(rèn)證通過的用戶信息)。
- 實(shí)現(xiàn)授權(quán)失敗后的處理邏輯,比如授權(quán)檢查失敗后返回的 401(未認(rèn)證),403(禁止訪問)等最終都是認(rèn)證方案的 ChallegeAsync方法以及ForbidAsync方法來處理,這些方法是IAuthenticationHandler里面定義的,這些流程在授權(quán)失敗為401/403的時(shí)候分別被授權(quán)流程調(diào)用。
授權(quán)流程主要如下幾個(gè)作用:
- 授權(quán)流程主要是檢查當(dāng)前用戶是否具備指定資源的訪問權(quán)限,如果授權(quán)檢查失敗,如401(未認(rèn)證),403(禁止訪問),那么最終會(huì)分別調(diào)用認(rèn)證方案的ChallegenAsync和ForbidAsync方法,也就是說,授權(quán)流程側(cè)重于授權(quán)失敗后的流程控制。
- 授權(quán)流程另外一個(gè)主要的任務(wù)是檢查授權(quán)策略是否均能檢驗(yàn)通過,如果一個(gè)資源通過AuthorizeAttribute的Policy屬性指定了一個(gè)或者多個(gè)授權(quán)策略,那么必須所有授權(quán)策略都驗(yàn)證通過才算授權(quán)成功,如果未指定授權(quán)策略,那么就驗(yàn)證默認(rèn)的授權(quán)策略是否能檢驗(yàn)通過,默認(rèn)的授權(quán)策略則是要求必須用戶認(rèn)證通過才允許訪問資源。
授權(quán)流程本質(zhì)上就是遍歷所有注入到容器中的IAuthorizationHandler(微軟默認(rèn)在AddAuthorization的時(shí)候向容器注入了:PassThroughAuthorizationHandler,這個(gè)授權(quán)處理程序遍歷AuthorizationHandlerContext.Requirements中所有實(shí)現(xiàn)了IAuthorizationHandler的Requirement類,并調(diào)用其HandleAsync方法來檢查當(dāng)前Requirement是否能校驗(yàn)通過),并對訪問指定資源所要滿足的所有策略中包含的Requirement進(jìn)行驗(yàn)證,如果所有策略包含的Requirement都驗(yàn)證通過,那么表示授權(quán)成功,這里的Requirement是指實(shí)現(xiàn)了IAuthorizationRequirement的類,這個(gè)接口是一個(gè)空接口,用于標(biāo)記Requirement使用。
以上就是asp.net core 認(rèn)證和授權(quán)實(shí)例詳解的詳細(xì)內(nèi)容,更多關(guān)于asp.net core 認(rèn)證授權(quán)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在asp.NET 中使用SMTP發(fā)送郵件的實(shí)現(xiàn)代碼
本文簡單介紹了SMTP協(xié)議(RFC2554)發(fā)送郵件的過程,并討論了在 .NET 中使用SMTP發(fā)送郵件由簡到繁的三種不同方案、各自可能遇到的問題及其解決辦法2011-05-05asp.net core MVC之實(shí)現(xiàn)基于token的認(rèn)證
這篇文章主要介紹了asp.net core MVC之實(shí)現(xiàn)基于token的認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-05-05.NET實(shí)現(xiàn)魔方游戲(一)之任意階魔方的表示
這篇文章主要介紹了.NET實(shí)現(xiàn)魔方游戲(一)之任意階魔方的表示 的相關(guān)資料,需要的朋友可以參考下2016-02-02.net開發(fā)人員常犯的錯(cuò)誤分析小結(jié)
我最新一直在和新手和入手級開發(fā)人員打交道,我注意到一些開發(fā)人員(甚至是老手)在粗心時(shí)常犯的錯(cuò)誤。這些錯(cuò)誤各不相同,從工具的使用到網(wǎng)絡(luò)服務(wù)的適當(dāng)應(yīng)用都有。以下是六個(gè)主要的開發(fā)錯(cuò)誤。2009-03-03