首頁 > 軟體

asp.net core專案授權流程詳解

2022-09-26 14:01:26

在上一篇 聊聊 asp.net core 認證和授權 中我們提到了認證和授權的基本概念,以及認證和授權的關係及他們之間的協同工作流程,在這篇文章中,我將通過分析asp.net core 3.1 授權流程的原始碼給大家介紹asp.net core 框架裡面授權流程的具體實現邏輯,本文並非講解具體的實戰應用,建議在使用過asp.net core 授權框架後在來閱讀本文收貨會更多。

一、授權流程用到的主要的幾個介面及類

  • IAuthorizationService,預設實現類: DefaultAuthorizationService,該類主要職責就是遍歷所有注入到容器的實現了IAuthorizationHandler介面的服務,並呼叫其HandleAsync方法來進行授權檢查,也就是說該類的主要職責就是檢查授權策略(AuthorizationPolicy)是否校驗通過,校驗通過則授權成功,否則授權失敗。
  • IAuthorizationPolicyProvider,預設實現類:DefaultAuthorizationPolicyProvider,負責根據策略名稱提供授權策略,以及提供預設授權策略等,內部就是從AuthorizationOptions內部的策略字典(Dictionary)中直接獲取。
  • IAuthorizationHandlerProvider,預設實現類:DefaultAuthorizationHandlerProvider,用於獲取已經註冊到容器中的所有實現了IAuthorizationHandler的授權服務,所有授權服務是通過建構函式依賴注入實現的(IEnumerable<IAuthorizationHandler>作為建構函式入參)
  • IAuthorizationHandler,預設實現類:PassThroughAuthorizationHandler,該類是AddAuthorization的時候預設註冊的授權處理程式(實現IAuthorizationHandler介面),用於遍歷授權策略中包含的所有的實現了IAuthorizationHandler的Requirement類,並呼叫其HandleAsync方法進行檢查Requirement授權是否成功,這裡的Requirement類是指實現了AuthorizationHandler<TRequirement>抽象基礎類別的Requirement類。
  • IAuthorizationEvaluator,預設實現類:DefaultAuthorizationEvaluator,執行授權流程,並對授權檢查結果進行檢查,如果是授權失敗,並且未認證則返回401,如果是授權失敗,但認證通過,則返回403
  • IAuthorizationHandlerContextFactory,預設實現類:DefaultAuthorizationHandlerContextFactory,用於建立AuthorizationHandlerContext物件的工廠類,AuthorizationHandlerContext 上下文中包含每次授權流程中要被校驗的所有的Requirement類。
  • AuthorizationMiddleware,負責對請求進行授權檢查的中介軟體.
  • AuthorizationOptions類,內部維護了一個策略字典(Dictionary)用於儲存所有註冊的策略,key為策略名稱,value為具體的策略(AuthorizationPolicy)
  • AuthorizationPolicy類,策略的具體表示,主要包含 AuthenticationSchemes 和 Requirements屬性,AuthenticationSchemes 表示執行該策略時採用什麼認證方案進行身分認證, Requirements 表示該策略要驗證的Requirement列表
  • AuthorizationPolicyBuilder類,該類主要是用於構建AuthorizationPolicy類,也就是用於構建具體策略的類,通過該類,可以指定該授權策略需要採用什麼認證方案進行認證,以及授權檢查時需要滿足那些Requirement。

二、授權服務註冊流程

首先找到 PolicyServiceCollectionExtensions 類,這個擴充套件方法類,對IServiceCollection介面進行了擴充套件,因此我們可以在Startup.cs 的ConfigureService方法中直接

services.AddAuthorization來註冊 授權相關服務。

// 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;
	}
        
        //當不想在應用程式中註冊授權策略時,直接呼叫此方法即可。
	public static IServiceCollection AddAuthorization(this IServiceCollection services)
	{
		return services.AddAuthorization(null);
	}
        //當需要在應用程式中註冊特定的授權策略時,呼叫這個方法,configure為Action型別的委託方法,入參為AuthorizationOptions 授權設定類,
       //可通過該類的AddPolicy方法來進行授權策略的註冊。
	public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure)
	{
		if (services == null)
		{
			throw new ArgumentNullException("services");
		}
		services.AddAuthorizationCore(configure);
		services.AddAuthorizationPolicyEvaluator();
		return services;
	}
}

可以看到,內部呼叫了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");
		}
                //以下這些服務便是上文中介紹的授權流程用到的主要服務類,及具體的預設實現類。
		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便是我們應用程式傳入的委託回撥方法,用於向AuthorizationOptions類新增授權策略。
		if (configure != null)
		{
			services.Configure(configure);
		}
		return services.AddAuthorizationCore();
	}
}

下面這個是應用註冊授權策略的常規流程的一個例子:

        public void ConfigureServices(IServiceCollection services)
        {
            //新增授權相關服務。
            services.AddAuthorization(options =>
            {
                //往AuthorizationOptions類中新增名為:adminPolicy的授權策略。
                //引數:authorizationPolicyBuilder 為AuthorizationPolicyBuilder類。
                options.AddPolicy("adminPolicy", authorizationPolicyBuilder =>
                {
                    authorizationPolicyBuilder.AddAuthenticationSchemes("Cookie");
                    //表示使用者必須屬於admin角色才能存取。
                    authorizationPolicyBuilder.AddRequirements(new RolesAuthorizationRequirement(new string[] { "admin" }));
                    //表示使用者宣告中包含名為cardNo的 Claim,並且值為23902390才允許存取,也就是 HttpContext.User.Claims 中包含cardNo,並且值為相應值才能存取。
                    authorizationPolicyBuilder.Requirements.Add(new ClaimsAuthorizationRequirement("cardNo", new string[] { "23902390" }));
                    //表示用使用者名稱必須是admin才允許存取,AuthorizationBuilder中海油RequireClaim、RequireRole等方法。
                    authorizationPolicyBuilder.RequireUserName("admin");
                    //只有以上3個Requirement同時滿足,該策略才算授權成功
                });
            });
        }

三、啟用授權流程

第二個步驟僅僅是將授權流程中用到的相關服務註冊到依賴注入容器中,以及應用設定授權策略,真正的啟用授權流程則需要通過 Startup.cs 類中的Configure方法中呼叫 app.UseAuthorization(); 進行開啟,本質上就是將 AuthorizationMiddleware 授權中介軟體,註冊到中介軟體管道中。

// 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);
                //註冊授權中介軟體。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(...)"));
		}
	}
}

要看授權流程的具體執行邏輯,我們還是要看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上標註的一個或者多個[Authorize]特性,
                //每個Authorize特性都有一個Policy屬性,用於指定一個或者多個授權策略,表示這些策略必須同時滿足才算授權通過,
                //Roles屬性則用於指定使用者角色列表,表示使用者必須屬於這些角色才允許存取,這裡的角色控制最終其實也是轉換為策略的形式去控制。
                //AuthenticationSchemes則用於指定認證方案列表,表示使用者存取該資源時採用這些認證方案進行身份認證
                //如:[Authorize(AuthenticationSchemes = "cookie", Policy = "adminPolicy", Roles = "admin")]
		IReadOnlyList<IAuthorizeData> authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
                //以下將Controller或者Action上的一個或者多個[Authorize]特性上指定的存取該資源所需要的滿足的Policy授權策略列表,
                //及存取該資源時使用者所需具備的角色列表,以及存取該資源時將採用的認證方案合併到一個策略物件中去,
                //也就是說最終返回的這個授權策略包含了存取該資源所需要滿足的所有授權策略列表,使用者所必須具備的所有使用者角色列表,以及採用的所有認證方案列表。
		AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
		if (policy == null)
		{
			await _next(context);
			return;
		}
		IPolicyEvaluator policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();
                //這裡首先對當前存取者進行使用者身份的認證,認證方案採用的是上面合併過後的一個或者多個認證方案進行認證。
		AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context);
                //如果允許匿名存取,則不再進行授權檢查。
		if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
		{
			await _next(context);
			return;
		}
                //這裡對policy中包含的所有授權策略進行一一檢查,如果全部驗證通過,則表示授權成功,允許使用者存取,
                //否則根據使用者是否已經登入來判定是讓使用者登入(401-Challenged)還是提示使用者沒許可權存取(403-Forbiden)
		PolicyAuthorizationResult policyAuthorizationResult = await policyEvaluator.AuthorizeAsync(policy, authenticationResult, context, endpoint);
		if (policyAuthorizationResult.Challenged)
		{
                        //如果授權失敗,且使用者身份未認證,且指定了認證方案,則呼叫特定的認證方案的Chanllege方法。
			if (policy.AuthenticationSchemes.Any())
			{
				foreach (string authenticationScheme in policy.AuthenticationSchemes)
				{
					await context.ChallengeAsync(authenticationScheme);
				}
			}
                        //如果該資源沒有指定任何認證方案,則採用預設的認證方案。
			else
			{
				await context.ChallengeAsync();
			}
		}
		else if (policyAuthorizationResult.Forbidden)
		{
                         //如果授權失敗,且使用者身份已認證,且指定了認證方案,則呼叫特定的認證方案的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特性指定的授權策略,使用者角色列表,認證方案進行合併,最終返回一個授權策略物件,這個授權策略包含了 存取該資源所需用到的所有認證方案,所有必須滿足的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]特性有指定授權策略,則將該授權策略新增到合併列表中。
			if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
			{
                                //IAuthorizationPolicyPovider 內部其實就是讀取 AuthorizationOptions的字典屬性中儲存的策略,key為策略名稱,value為相應的授權策略。
				AuthorizationPolicy authorizationPolicy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
				if (authorizationPolicy == null)
				{
					throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
				}
                                //其實就是將 Requirements 和 AuthenticationSchemes(認證方案列表) 新增到合併後的Requirements及授權方案列表中去。
				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());
					}
				}
			}
                        //如果當前Authorize特性既沒有指定授權策略,也沒有指定角色列表,那麼採用預設授權策略(預設授權策略其實就是要求使用者身份必須被認證通過)
			if (flag2)
			{
				AuthorizationPolicyBuilder authorizationPolicyBuilder = policyBuilder;
				authorizationPolicyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
			}
		}
	}
        //如果一個Controller或者Action沒有指定任何[Authorize]特性,那麼如果啟用了授權流程,則採用Fallback策略進行授權檢查。
	if (policyBuilder == null)
	{
		AuthorizationPolicy authorizationPolicy2 = await policyProvider.GetFallbackPolicyAsync();
		if (authorizationPolicy2 != null)
		{
			return authorizationPolicy2;
		}
	}
	return policyBuilder?.Build();
}

以下是對 IPolicyEvaluator.AuthenticateAsync方法的說明,該方法主要是對存取該資源所指定的認證方案列表進行一一認證,並將認證結果產生的使用者資訊進行合併,預設實現類是:PolicyEvaluator,該介面主要定義了兩個方法,一個是:AuthenticateAsync,負責對當前存取者進行身份認證,一個是AuthorizeAsync,負責對當前存取者進行授權檢查,通常要授權成功,必須要求使用者先進行身份認證,認證通過並且授前檢查通過才允許存取,但認證不是必須的,如果你要自定義授權邏輯的話,你甚至可以不認證使用者身份也授權其進行存取,但實際開發中通常不會這麼做,這裡僅僅只是闡述兩者之間的一些聯絡,之所以預設標記了Authorize特性並且啟用授權流程後,要求使用者必須登入(身份認證)是因為用[Authorize]特性標記控制器後,執行的是預設策略,而預設策略就是必須要求使用者進行身份認證。

// 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;
	}
        //引數policy是一個合併後的策略,裡面包含了存取該資源所採用的所有認證方案列表。
	public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
	{
		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();
	}
        //這個是對合並後的授權策略進行授權檢查的方法,內部還是去呼叫了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的說明,主要負責對合並後的授權策略(AuthorizationPolicy)中的Requirements進行一一檢查,全部檢查通過,則授權成功,預設實現類是: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;
	}
        //這個就是檢查授權策略的核心邏輯了,流程就是讀取 依賴注入容器中所有註冊的實現了IAuthorizationHandler介面的服務,並對其遍歷並分別呼叫服務的HandleAsync方法。
        //微軟預設注入的IAuthorizationHandler的實現類是: PassThroughAuthorizationHandler,該類主要是找出Requirements中實現了IAuthorizationHandler的Requirement類,並對其呼叫HandleAsync方法來檢查這類Requirement是否授權通過。
	public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements)
	{
		if (requirements == null)
		{
			throw new ArgumentNullException("requirements");
		}
                //AuthorizationHandlerContext 上下文中,包含了所有需要進行授權檢查的Requirement。
		AuthorizationHandlerContext authContext = _contextFactory.CreateContext(requirements, user, resource);
		foreach (IAuthorizationHandler item in await _handlers.GetHandlersAsync(authContext))
		{
			await item.HandleAsync(authContext);
                        //如果授權檢查失敗,並且InvokeHandlersAfterFailure為false時,即某一個Requirement檢查失敗時,是否繼續執行剩餘的Requirement檢查。
			if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)
			{
				break;
			}
		}
                //這裡主要是檢查是否所有的Requirement都驗證通過,如果都驗證通過,那麼返回授權成功,否則返回授權失敗。
		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的預設實現類:DefaultAuthorizationEvaluator的原始碼,負責檢查是否所有Requirement類都驗證通過,如果存在部分未驗證通過,則返回授權失敗。

// Microsoft.AspNetCore.Authorization.DefaultAuthorizationEvaluator
using Microsoft.AspNetCore.Authorization;

public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator
{
	public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
	{
                //看HasSucceded原始碼,其實要授權成功,必須沒有顯式呼叫授權失敗的方法。
		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);
	}
        //如果呼叫了此方法,那麼直接進入授權失敗流程了,也就是顯式告訴應用授權失敗了。
	public virtual void Fail()
	{
		_failCalled = true;
	}
        //某個Requirement驗證成功,那麼將會呼叫該方法,並從未驗證的Requirements列表中移除。
	public virtual void Succeed(IAuthorizationRequirement requirement)
	{
		_succeedCalled = true;
		_pendingRequirements.Remove(requirement);
	}
}

以下是:PassThroughAuthorizationHandler的原始碼,邏輯比較簡單,就是讀取Requirements中所有實現了IAuthorizationHandler介面的Requirement類,並呼叫HandleAsync方法,這就是為什麼我們在[Authrize(Roles="admin")]特性中指定角色列表的時候,並在 AuthorizationPolicy.CombineAsync  中被動態合併到策略物件中後,能被執行的原因,Roles屬性指定的角色列表最終會被動態轉換成:RolesAuthorizationRequirement,並將這個Requirement合併到最終的策略中去,微軟 Microsoft.AspNetCore.Authorization.Infrastructure 名稱空間下提供了 ClaimsAuthorizationRequirement 、DenyAnonymousAuthorizationRequirement 等Requirement類,其中 DenyAnonymousAuthorizationRequirement 就是預設策略所包含的Requirement,也就是要求使用者必須登入進行身份認證後才能進行存取,如果被存取的資源未指定授權策略的情況下。

// 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方法呼叫,基礎類別中的HandleAsync則是找出存取授權策略中所有屬於該型別的Requirement,然後分別呼叫其 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;
	}
}

以下是應用開啟授權流程的一個範例:

        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();
           //啟用授權流程
            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                //RequireAuthorization表示所有Controller都需要登入後才能存取。
                endpoints.MapDefaultControllerRoute().RequireAuthorization();
            });
        }

總結來說,授權流程首先就是 讀取 Controller 或者 Action 上指定的一個或者多個 [Authorize] 特性,並把這些特性指定的授權策略中所包含的Requirement類(實現了IAuthorizationRequirement介面的類)統一合併到一個策略物件中去,對於未指定具體策略的[Authorize]特性,則採用預設的授權策略(要求使用者必須登入認證),同時也把這些特性中指定的認證方案進行統一合併到一個策略物件中去,然後對當前使用者對合並後的策略中所包含的認證方案一一進行身份認證,並將身份認證結果進行一一合併,然後就是對合並後的授權策略中的Requirement一一進行檢查,如果全部授權通過,並且沒有顯式呼叫授權失敗的方法,則授權成功。

到此這篇關於asp.net core授權流程的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com