asp.net core項目授權(quán)流程詳解
在上一篇 聊聊 asp.net core 認證和授權(quán) 中我們提到了認證和授權(quán)的基本概念,以及認證和授權(quán)的關(guān)系及他們之間的協(xié)同工作流程,在這篇文章中,我將通過分析asp.net core 3.1 授權(quán)流程的源碼給大家介紹asp.net core 框架里面授權(quán)流程的具體實現(xiàn)邏輯,本文并非講解具體的實戰(zhàn)應(yīng)用,建議在使用過asp.net core 授權(quán)框架后在來閱讀本文收貨會更多。
一、授權(quán)流程用到的主要的幾個接口及類
- IAuthorizationService,默認實現(xiàn)類: DefaultAuthorizationService,該類主要職責(zé)就是遍歷所有注入到容器的實現(xiàn)了IAuthorizationHandler接口的服務(wù),并調(diào)用其HandleAsync方法來進行授權(quán)檢查,也就是說該類的主要職責(zé)就是檢查授權(quán)策略(AuthorizationPolicy)是否校驗通過,校驗通過則授權(quán)成功,否則授權(quán)失敗。
- IAuthorizationPolicyProvider,默認實現(xiàn)類:DefaultAuthorizationPolicyProvider,負責(zé)根據(jù)策略名稱提供授權(quán)策略,以及提供默認授權(quán)策略等,內(nèi)部就是從AuthorizationOptions內(nèi)部的策略字典(Dictionary)中直接獲取。
- IAuthorizationHandlerProvider,默認實現(xiàn)類:DefaultAuthorizationHandlerProvider,用于獲取已經(jīng)注冊到容器中的所有實現(xiàn)了IAuthorizationHandler的授權(quán)服務(wù),所有授權(quán)服務(wù)是通過構(gòu)造函數(shù)依賴注入實現(xiàn)的(IEnumerable<IAuthorizationHandler>作為構(gòu)造函數(shù)入?yún)ⅲ?/li>
- IAuthorizationHandler,默認實現(xiàn)類:PassThroughAuthorizationHandler,該類是AddAuthorization的時候默認注冊的授權(quán)處理程序(實現(xiàn)IAuthorizationHandler接口),用于遍歷授權(quán)策略中包含的所有的實現(xiàn)了IAuthorizationHandler的Requirement類,并調(diào)用其HandleAsync方法進行檢查Requirement授權(quán)是否成功,這里的Requirement類是指實現(xiàn)了AuthorizationHandler<TRequirement>抽象基類的Requirement類。
- IAuthorizationEvaluator,默認實現(xiàn)類:DefaultAuthorizationEvaluator,執(zhí)行授權(quán)流程,并對授權(quán)檢查結(jié)果進行檢查,如果是授權(quán)失敗,并且未認證則返回401,如果是授權(quán)失敗,但認證通過,則返回403
- IAuthorizationHandlerContextFactory,默認實現(xiàn)類:DefaultAuthorizationHandlerContextFactory,用于創(chuàng)建AuthorizationHandlerContext對象的工廠類,AuthorizationHandlerContext 上下文中包含每次授權(quán)流程中要被校驗的所有的Requirement類。
- AuthorizationMiddleware,負責(zé)對請求進行授權(quán)檢查的中間件.
- AuthorizationOptions類,內(nèi)部維護了一個策略字典(Dictionary)用于存儲所有注冊的策略,key為策略名稱,value為具體的策略(AuthorizationPolicy)
- AuthorizationPolicy類,策略的具體表示,主要包含 AuthenticationSchemes 和 Requirements屬性,AuthenticationSchemes 表示執(zhí)行該策略時采用什么認證方案進行身分認證, Requirements 表示該策略要驗證的Requirement列表
- AuthorizationPolicyBuilder類,該類主要是用于構(gòu)建AuthorizationPolicy類,也就是用于構(gòu)建具體策略的類,通過該類,可以指定該授權(quán)策略需要采用什么認證方案進行認證,以及授權(quán)檢查時需要滿足那些Requirement。
二、授權(quán)服務(wù)注冊流程
首先找到 PolicyServiceCollectionExtensions 類,這個擴展方法類,對IServiceCollection接口進行了擴展,因此我們可以在Startup.cs 的ConfigureService方法中直接
services.AddAuthorization來注冊 授權(quán)相關(guān)服務(wù)。
// Microsoft.Extensions.DependencyInjection.PolicyServiceCollectionExtensions using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; public static class PolicyServiceCollectionExtensions { public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException("services"); } services.TryAddSingleton<AuthorizationPolicyMarkerService>(); services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>()); return services; } //當(dāng)不想在應(yīng)用程序中注冊授權(quán)策略時,直接調(diào)用此方法即可。 public static IServiceCollection AddAuthorization(this IServiceCollection services) { return services.AddAuthorization(null); } //當(dāng)需要在應(yīng)用程序中注冊特定的授權(quán)策略時,調(diào)用這個方法,configure為Action類型的委托方法,入?yún)锳uthorizationOptions 授權(quán)配置類, //可通過該類的AddPolicy方法來進行授權(quán)策略的注冊。 public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure) { if (services == null) { throw new ArgumentNullException("services"); } services.AddAuthorizationCore(configure); services.AddAuthorizationPolicyEvaluator(); return services; } }
可以看到,內(nèi)部調(diào)用了AddAuthorizationCore方法,這個擴展方法定義在:AuthorizationServiceCollectionExtensions 類
// Microsoft.Extensions.DependencyInjection.AuthorizationServiceCollectionExtensions using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; public static class AuthorizationServiceCollectionExtensions { public static IServiceCollection AddAuthorizationCore(this IServiceCollection services) { if (services == null) { throw new ArgumentNullException("services"); } //以下這些服務(wù)便是上文中介紹的授權(quán)流程用到的主要服務(wù)類,及具體的默認實現(xiàn)類。 services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>()); services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>()); services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>()); services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>()); services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>()); services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>()); return services; } public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action<AuthorizationOptions> configure) { if (services == null) { throw new ArgumentNullException("services"); } //這里的configure便是我們應(yīng)用程序傳入的委托回調(diào)方法,用于向AuthorizationOptions類添加授權(quán)策略。 if (configure != null) { services.Configure(configure); } return services.AddAuthorizationCore(); } }
下面這個是應(yīng)用注冊授權(quán)策略的常規(guī)流程的一個例子:
public void ConfigureServices(IServiceCollection services) { //添加授權(quán)相關(guān)服務(wù)。 services.AddAuthorization(options => { //往AuthorizationOptions類中添加名為:adminPolicy的授權(quán)策略。 //參數(shù):authorizationPolicyBuilder 為AuthorizationPolicyBuilder類。 options.AddPolicy("adminPolicy", authorizationPolicyBuilder => { authorizationPolicyBuilder.AddAuthenticationSchemes("Cookie"); //表示用戶必須屬于admin角色才能訪問。 authorizationPolicyBuilder.AddRequirements(new RolesAuthorizationRequirement(new string[] { "admin" })); //表示用戶聲明中包含名為cardNo的 Claim,并且值為23902390才允許訪問,也就是 HttpContext.User.Claims 中包含cardNo,并且值為相應(yīng)值才能訪問。 authorizationPolicyBuilder.Requirements.Add(new ClaimsAuthorizationRequirement("cardNo", new string[] { "23902390" })); //表示用用戶名必須是admin才允許訪問,AuthorizationBuilder中海油RequireClaim、RequireRole等方法。 authorizationPolicyBuilder.RequireUserName("admin"); //只有以上3個Requirement同時滿足,該策略才算授權(quán)成功 }); }); }
三、啟用授權(quán)流程
第二個步驟僅僅是將授權(quán)流程中用到的相關(guān)服務(wù)注冊到依賴注入容器中,以及應(yīng)用配置授權(quán)策略,真正的啟用授權(quán)流程則需要通過 Startup.cs 類中的Configure方法中調(diào)用 app.UseAuthorization(); 進行開啟,本質(zhì)上就是將 AuthorizationMiddleware 授權(quán)中間件,注冊到中間件管道中。
// Microsoft.AspNetCore.Builder.AuthorizationAppBuilderExtensions using System; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Builder; public static class AuthorizationAppBuilderExtensions { public static IApplicationBuilder UseAuthorization(this IApplicationBuilder app) { if (app == null) { throw new ArgumentNullException("app"); } VerifyServicesRegistered(app); //注冊授權(quán)中間件。AuthorizationMiddleware return app.UseMiddleware<AuthorizationMiddleware>(Array.Empty<object>()); } private static void VerifyServicesRegistered(IApplicationBuilder app) { if (app.ApplicationServices.GetService(typeof(AuthorizationPolicyMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatException_UnableToFindServices("IServiceCollection", "AddAuthorization", "ConfigureServices(...)")); } } }
要看授權(quán)流程的具體執(zhí)行邏輯,我們還是要看AuthorizationMiddleware類。
// 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; } //這里獲取Controller或者Action上標(biāo)注的一個或者多個[Authorize]特性, //每個Authorize特性都有一個Policy屬性,用于指定一個或者多個授權(quán)策略,表示這些策略必須同時滿足才算授權(quán)通過, //Roles屬性則用于指定用戶角色列表,表示用戶必須屬于這些角色才允許訪問,這里的角色控制最終其實也是轉(zhuǎn)換為策略的形式去控制。 //AuthenticationSchemes則用于指定認證方案列表,表示用戶訪問該資源時采用這些認證方案進行身份認證 //如:[Authorize(AuthenticationSchemes = "cookie", Policy = "adminPolicy", Roles = "admin")] IReadOnlyList<IAuthorizeData> authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>(); //以下將Controller或者Action上的一個或者多個[Authorize]特性上指定的訪問該資源所需要的滿足的Policy授權(quán)策略列表, //及訪問該資源時用戶所需具備的角色列表,以及訪問該資源時將采用的認證方案合并到一個策略對象中去, //也就是說最終返回的這個授權(quán)策略包含了訪問該資源所需要滿足的所有授權(quán)策略列表,用戶所必須具備的所有用戶角色列表,以及采用的所有認證方案列表。 AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData); if (policy == null) { await _next(context); return; } IPolicyEvaluator policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>(); //這里首先對當(dāng)前訪問者進行用戶身份的認證,認證方案采用的是上面合并過后的一個或者多個認證方案進行認證。 AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context); //如果允許匿名訪問,則不再進行授權(quán)檢查。 if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null) { await _next(context); return; } //這里對policy中包含的所有授權(quán)策略進行一一檢查,如果全部驗證通過,則表示授權(quán)成功,允許用戶訪問, //否則根據(jù)用戶是否已經(jīng)登錄來判定是讓用戶登錄(401-Challenged)還是提示用戶沒權(quán)限訪問(403-Forbiden) PolicyAuthorizationResult policyAuthorizationResult = await policyEvaluator.AuthorizeAsync(policy, authenticationResult, context, endpoint); if (policyAuthorizationResult.Challenged) { //如果授權(quán)失敗,且用戶身份未認證,且指定了認證方案,則調(diào)用特定的認證方案的Chanllege方法。 if (policy.AuthenticationSchemes.Any()) { foreach (string authenticationScheme in policy.AuthenticationSchemes) { await context.ChallengeAsync(authenticationScheme); } } //如果該資源沒有指定任何認證方案,則采用默認的認證方案。 else { await context.ChallengeAsync(); } } else if (policyAuthorizationResult.Forbidden) { //如果授權(quán)失敗,且用戶身份已認證,且指定了認證方案,則調(diào)用特定的認證方案的Forbid方法來處理禁止訪問的處理邏輯。 if (policy.AuthenticationSchemes.Any()) { foreach (string authenticationScheme2 in policy.AuthenticationSchemes) { await context.ForbidAsync(authenticationScheme2); } } //如果該資源沒有指定任何認證方案,則采用默認的認證方案來處理禁止訪問的邏輯 else { await context.ForbidAsync(); } } else { await _next(context); } } }
以下是AuthorizationPolicy.CombineAsync方法的詳細說明,該方法主要是用于將一個或者多個Authorize特性指定的授權(quán)策略,用戶角色列表,認證方案進行合并,最終返回一個授權(quán)策略對象,這個授權(quán)策略包含了 訪問該資源所需用到的所有認證方案,所有必須滿足的Requirement.
// Microsoft.AspNetCore.Authorization.AuthorizationPolicy using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) { if (policyProvider == null) { throw new ArgumentNullException("policyProvider"); } if (authorizeData == null) { throw new ArgumentNullException("authorizeData"); } bool flag = false; IList<IAuthorizeData> list = authorizeData as IList<IAuthorizeData>; if (list != null) { flag = list.Count == 0; } AuthorizationPolicyBuilder policyBuilder = null; if (!flag) { //這里遍歷Controller或者Action上的一個或者多個[Authorize]特性 foreach (IAuthorizeData authorizeDatum in authorizeData) { if (policyBuilder == null) { policyBuilder = new AuthorizationPolicyBuilder(); } bool flag2 = true; //如果某個[Authorize]特性有指定授權(quán)策略,則將該授權(quán)策略添加到合并列表中。 if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy)) { //IAuthorizationPolicyPovider 內(nèi)部其實就是讀取 AuthorizationOptions的字典屬性中保存的策略,key為策略名稱,value為相應(yīng)的授權(quán)策略。 AuthorizationPolicy authorizationPolicy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy); if (authorizationPolicy == null) { throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy)); } //其實就是將 Requirements 和 AuthenticationSchemes(認證方案列表) 添加到合并后的Requirements及授權(quán)方案列表中去。 policyBuilder.Combine(authorizationPolicy); flag2 = false; } string[] array = authorizeDatum.Roles?.Split(","); if (array != null && array.Any()) { IEnumerable<string> roles = from r in array where !string.IsNullOrWhiteSpace(r) select r.Trim(); //如果一個[Authorize]特性指定了Roles屬性,那么將屬性中指定的一個或者多個角色列表添加到合并后的角色列表中去。 //看RequireRole,其實就是往合并后的Requirements中添加了一個名為:RolesAuthorizationRequirement的Requirement policyBuilder.RequireRole(roles); flag2 = false; } string[] array2 = authorizeDatum.AuthenticationSchemes?.Split(","); if (array2 != null && array2.Any()) { string[] array3 = array2; //將Authorize特性中指定的一個或者多個認證方案添加到合并后的認證方案列表中。 foreach (string text in array3) { if (!string.IsNullOrWhiteSpace(text)) { policyBuilder.AuthenticationSchemes.Add(text.Trim()); } } } //如果當(dāng)前Authorize特性既沒有指定授權(quán)策略,也沒有指定角色列表,那么采用默認授權(quán)策略(默認授權(quán)策略其實就是要求用戶身份必須被認證通過) if (flag2) { AuthorizationPolicyBuilder authorizationPolicyBuilder = policyBuilder; authorizationPolicyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync()); } } } //如果一個Controller或者Action沒有指定任何[Authorize]特性,那么如果啟用了授權(quán)流程,則采用Fallback策略進行授權(quán)檢查。 if (policyBuilder == null) { AuthorizationPolicy authorizationPolicy2 = await policyProvider.GetFallbackPolicyAsync(); if (authorizationPolicy2 != null) { return authorizationPolicy2; } } return policyBuilder?.Build(); }
以下是對 IPolicyEvaluator.AuthenticateAsync方法的說明,該方法主要是對訪問該資源所指定的認證方案列表進行一一認證,并將認證結(jié)果產(chǎn)生的用戶信息進行合并,默認實現(xiàn)類是:PolicyEvaluator,該接口主要定義了兩個方法,一個是:AuthenticateAsync,負責(zé)對當(dāng)前訪問者進行身份認證,一個是AuthorizeAsync,負責(zé)對當(dāng)前訪問者進行授權(quán)檢查,通常要授權(quán)成功,必須要求用戶先進行身份認證,認證通過并且授前檢查通過才允許訪問,但認證不是必須的,如果你要自定義授權(quán)邏輯的話,你甚至可以不認證用戶身份也授權(quán)其進行訪問,但實際開發(fā)中通常不會這么做,這里僅僅只是闡述兩者之間的一些聯(lián)系,之所以默認標(biāo)記了Authorize特性并且啟用授權(quán)流程后,要求用戶必須登錄(身份認證)是因為用[Authorize]特性標(biāo)記控制器后,執(zhí)行的是默認策略,而默認策略就是必須要求用戶進行身份認證。
// 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; } //參數(shù)policy是一個合并后的策略,里面包含了訪問該資源所采用的所有認證方案列表。 public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) { if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0) { ClaimsPrincipal newPrincipal = null; //如果被訪問的資源指定了身份認證方案,則采用指定的身份認證方案一一進行認證,并把所有身份認證結(jié)果進行合并。 //認證流程中添加的一個或者多個認證方案,可以在授權(quán)流程中被調(diào)用進行用戶身份的認證,雖然一個應(yīng)用可以添加多個認證方案, //但默認情況下,認證流程只會調(diào)用默認的認證方案進行身份認證。 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(); } //如果當(dāng)前被訪問的資源沒有指定采用何種認證方案進行身份認證,則默認采用認證流程產(chǎn)生的身份認證信息。 return (context.User?.Identity?.IsAuthenticated).GetValueOrDefault() ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User")) : AuthenticateResult.NoResult(); } //這個是對合并后的授權(quán)策略進行授權(quán)檢查的方法,內(nèi)部還是去調(diào)用了IAuthorizationService.AuthorizeAsync方法。 public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource) { if (policy == null) { throw new ArgumentNullException("policy"); } if ((await _authorization.AuthorizeAsync(context.User, resource, policy)).Succeeded) { return PolicyAuthorizationResult.Success(); } return authenticationResult.Succeeded ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge(); } }
以下是IAuthorizationService.AuthorizeAsync的說明,主要負責(zé)對合并后的授權(quán)策略(AuthorizationPolicy)中的Requirements進行一一檢查,全部檢查通過,則授權(quán)成功,默認實現(xiàn)類是:DefaultAuthorizationService
// Microsoft.AspNetCore.Authorization.DefaultAuthorizationService using System; using System.Collections.Generic; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; public class DefaultAuthorizationService : IAuthorizationService { private readonly AuthorizationOptions _options; private readonly IAuthorizationHandlerContextFactory _contextFactory; private readonly IAuthorizationHandlerProvider _handlers; private readonly IAuthorizationEvaluator _evaluator; private readonly IAuthorizationPolicyProvider _policyProvider; private readonly ILogger _logger; public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IAuthorizationHandlerProvider handlers, ILogger<DefaultAuthorizationService> logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator, IOptions<AuthorizationOptions> options) { if (options == null) { throw new ArgumentNullException("options"); } if (policyProvider == null) { throw new ArgumentNullException("policyProvider"); } if (handlers == null) { throw new ArgumentNullException("handlers"); } if (logger == null) { throw new ArgumentNullException("logger"); } if (contextFactory == null) { throw new ArgumentNullException("contextFactory"); } if (evaluator == null) { throw new ArgumentNullException("evaluator"); } _options = options.Value; _handlers = handlers; _policyProvider = policyProvider; _logger = logger; _evaluator = evaluator; _contextFactory = contextFactory; } //這個就是檢查授權(quán)策略的核心邏輯了,流程就是讀取 依賴注入容器中所有注冊的實現(xiàn)了IAuthorizationHandler接口的服務(wù),并對其遍歷并分別調(diào)用服務(wù)的HandleAsync方法。 //微軟默認注入的IAuthorizationHandler的實現(xiàn)類是: PassThroughAuthorizationHandler,該類主要是找出Requirements中實現(xiàn)了IAuthorizationHandler的Requirement類,并對其調(diào)用HandleAsync方法來檢查這類Requirement是否授權(quán)通過。 public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements) { if (requirements == null) { throw new ArgumentNullException("requirements"); } //AuthorizationHandlerContext 上下文中,包含了所有需要進行授權(quán)檢查的Requirement。 AuthorizationHandlerContext authContext = _contextFactory.CreateContext(requirements, user, resource); foreach (IAuthorizationHandler item in await _handlers.GetHandlersAsync(authContext)) { await item.HandleAsync(authContext); //如果授權(quán)檢查失敗,并且InvokeHandlersAfterFailure為false時,即某一個Requirement檢查失敗時,是否繼續(xù)執(zhí)行剩余的Requirement檢查。 if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed) { break; } } //這里主要是檢查是否所有的Requirement都驗證通過,如果都驗證通過,那么返回授權(quán)成功,否則返回授權(quán)失敗。 AuthorizationResult authorizationResult = _evaluator.Evaluate(authContext); if (authorizationResult.Succeeded) { _logger.UserAuthorizationSucceeded(); } else { _logger.UserAuthorizationFailed(); } return authorizationResult; } public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName) { if (policyName == null) { throw new ArgumentNullException("policyName"); } AuthorizationPolicy authorizationPolicy = await _policyProvider.GetPolicyAsync(policyName); if (authorizationPolicy == null) { throw new InvalidOperationException("No policy found: " + policyName + "."); } return await this.AuthorizeAsync(user, resource, authorizationPolicy); } }
以下是IAuthorizationEvaluator的默認實現(xiàn)類:DefaultAuthorizationEvaluator的源碼,負責(zé)檢查是否所有Requirement類都驗證通過,如果存在部分未驗證通過,則返回授權(quán)失敗。
// Microsoft.AspNetCore.Authorization.DefaultAuthorizationEvaluator using Microsoft.AspNetCore.Authorization; public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator { public AuthorizationResult Evaluate(AuthorizationHandlerContext context) { //看HasSucceded源碼,其實要授權(quán)成功,必須沒有顯式調(diào)用授權(quán)失敗的方法。 if (!context.HasSucceeded) { return AuthorizationResult.Failed(context.HasFailed ? AuthorizationFailure.ExplicitFail() : AuthorizationFailure.Failed(context.PendingRequirements)); } return AuthorizationResult.Success(); } }
以下是:AuthorizationHandlerContext的源碼
// Microsoft.AspNetCore.Authorization.AuthorizationHandlerContext using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using Microsoft.AspNetCore.Authorization; public class AuthorizationHandlerContext { private HashSet<IAuthorizationRequirement> _pendingRequirements; private bool _failCalled; private bool _succeedCalled; public virtual IEnumerable<IAuthorizationRequirement> Requirements { get; } public virtual ClaimsPrincipal User { get; } public virtual object Resource { get; } public virtual IEnumerable<IAuthorizationRequirement> PendingRequirements => _pendingRequirements; public virtual bool HasFailed => _failCalled; public virtual bool HasSucceeded { get { if (!_failCalled && _succeedCalled) { return !PendingRequirements.Any(); } return false; } } public AuthorizationHandlerContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource) { if (requirements == null) { throw new ArgumentNullException("requirements"); } Requirements = requirements; User = user; Resource = resource; _pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements); } //如果調(diào)用了此方法,那么直接進入授權(quán)失敗流程了,也就是顯式告訴應(yīng)用授權(quán)失敗了。 public virtual void Fail() { _failCalled = true; } //某個Requirement驗證成功,那么將會調(diào)用該方法,并從未驗證的Requirements列表中移除。 public virtual void Succeed(IAuthorizationRequirement requirement) { _succeedCalled = true; _pendingRequirements.Remove(requirement); } }
以下是:PassThroughAuthorizationHandler的源碼,邏輯比較簡單,就是讀取Requirements中所有實現(xiàn)了IAuthorizationHandler接口的Requirement類,并調(diào)用HandleAsync方法,這就是為什么我們在[Authrize(Roles="admin")]特性中指定角色列表的時候,并在 AuthorizationPolicy.CombineAsync 中被動態(tài)合并到策略對象中后,能被執(zhí)行的原因,Roles屬性指定的角色列表最終會被動態(tài)轉(zhuǎn)換成:RolesAuthorizationRequirement,并將這個Requirement合并到最終的策略中去,微軟 Microsoft.AspNetCore.Authorization.Infrastructure 命名空間下提供了 ClaimsAuthorizationRequirement 、DenyAnonymousAuthorizationRequirement 等Requirement類,其中 DenyAnonymousAuthorizationRequirement 就是默認策略所包含的Requirement,也就是要求用戶必須登錄進行身份認證后才能進行訪問,如果被訪問的資源未指定授權(quán)策略的情況下。
// Microsoft.AspNetCore.Authorization.Infrastructure.PassThroughAuthorizationHandler using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; public class PassThroughAuthorizationHandler : IAuthorizationHandler { public async Task HandleAsync(AuthorizationHandlerContext context) { foreach (IAuthorizationHandler item in context.Requirements.OfType<IAuthorizationHandler>()) { await item.HandleAsync(context); } } }
以下是RolesRequirement類的源碼,表示用戶必須屬于指定角色才能進行訪問特定資源,HandleRequirementAsync被AuthorizationHandler抽象基類中的HandleAsync方法調(diào)用,基類中的HandleAsync則是找出訪問授權(quán)策略中所有屬于該類型的Requirement,然后分別調(diào)用其 HandleRequirementAsync方法。
// Microsoft.AspNetCore.Authorization.Infrastructure.RolesAuthorizationRequirement using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement { public IEnumerable<string> AllowedRoles { get; } public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles) { if (allowedRoles == null) { throw new ArgumentNullException("allowedRoles"); } if (allowedRoles.Count() == 0) { throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty); } AllowedRoles = allowedRoles; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement) { if (context.User != null) { bool flag = false; if (requirement.AllowedRoles != null && requirement.AllowedRoles.Any()) { flag = requirement.AllowedRoles.Any((string r) => context.User.IsInRole(r)); } if (flag) { context.Succeed(requirement); } } return Task.CompletedTask; } }
以下是應(yīng)用開啟授權(quán)流程的一個示例:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); //啟用認證流程。 app.UseAuthentication(); //啟用授權(quán)流程 app.UseAuthorization(); app.UseEndpoints(endpoints => { //RequireAuthorization表示所有Controller都需要登錄后才能訪問。 endpoints.MapDefaultControllerRoute().RequireAuthorization(); }); }
總結(jié)來說,授權(quán)流程首先就是 讀取 Controller 或者 Action 上指定的一個或者多個 [Authorize] 特性,并把這些特性指定的授權(quán)策略中所包含的Requirement類(實現(xiàn)了IAuthorizationRequirement接口的類)統(tǒng)一合并到一個策略對象中去,對于未指定具體策略的[Authorize]特性,則采用默認的授權(quán)策略(要求用戶必須登錄認證),同時也把這些特性中指定的認證方案進行統(tǒng)一合并到一個策略對象中去,然后對當(dāng)前用戶對合并后的策略中所包含的認證方案一一進行身份認證,并將身份認證結(jié)果進行一一合并,然后就是對合并后的授權(quán)策略中的Requirement一一進行檢查,如果全部授權(quán)通過,并且沒有顯式調(diào)用授權(quán)失敗的方法,則授權(quán)成功。
到此這篇關(guān)于asp.net core授權(quán)流程的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持。
相關(guān)文章:
1. asp.net core應(yīng)用docke部署到centos7的全過程2. ASP.Net Core對USB攝像頭進行截圖3. ASP.NET Core自定義中間件的方式詳解4. 理解ASP.NET Core 配置系統(tǒng)5. asp.net core 中的Jwt(Json Web Token)的使用詳解6. Asp.net Core項目配置HTTPS支持7. 如何將asp.net core程序部署到Linux服務(wù)器8. asp.net core 認證和授權(quán)實例詳解9. 在Asp.net core項目中使用WebSocket10. 如何使用ASP.NET Core 配置文件
