代码编织梦想

🚀 优质资源分享 🚀

学习路线指引(点击解锁)知识定位人群定位
🧡 Python实战微信订餐小程序 🧡进阶级本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。
💛Python量化交易实战💛入门级手把手带你打造一个易扩展、更安全、效率更高的量化交易系统

在上一篇 聊聊 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,默认实现类:PassThroughAuthorizationHandler,该类是AddAuthorization的时候默认注册的授权处理程序(实现IAuthorizationHandler接口),用于遍历授权策略中包含的所有的实现了IAuthorizationHandler的Requirement类,并调用其HandleAsync方法进行检查Requirement授权是否成功,这里的Requirement类是指实现了AuthorizationHandler抽象基类的Requirement类。
  • IAuthorizationEvaluator,默认实现类:DefaultAuthorizationEvaluator,执行授权流程,并对授权检查结果进行检查,如果是授权失败,并且未认证则返回401,如果是授权失败,但认证通过,则返回403
  • IAuthorizationHandlerContextFactory,默认实现类:DefaultAuthorizationHandlerContextFactory
  • 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();
 services.TryAdd(ServiceDescriptor.Transient());
 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 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());
 services.TryAdd(ServiceDescriptor.Transient());
 services.TryAdd(ServiceDescriptor.Transient());
 services.TryAdd(ServiceDescriptor.Transient());
 services.TryAdd(ServiceDescriptor.Transient());
 services.TryAddEnumerable(ServiceDescriptor.Transient());
 return services;
 }

 public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action 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(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 authorizeData = endpoint?.Metadata.GetOrderedMetadata() ?? Array.Empty();
 //以下将Controller或者Action上的一个或者多个[Authorize]特性上指定的访问该资源所需要的满足的Policy授权策略列表,
 //及访问该资源时用户所需具备的角色列表,以及访问该资源时将采用的认证方案合并到一个策略对象中去,
 //也就是说最终返回的这个授权策略包含了访问该资源所需要满足的所有授权策略列表,用户所必须具备的所有用户角色列表,以及采用的所有认证方案列表。
 AuthorizationPolicy policy = await AuthorizationPolicy.CombineAsync(\_policyProvider, authorizeData);
 if (policy == null)
 {
 await \_next(context);
 return;
 }
 IPolicyEvaluator policyEvaluator = context.RequestServices.GetRequiredService();
 //这里首先对当前访问者进行用户身份的认证,认证方案采用的是上面合并过后的一个或者多个认证方案进行认证。
 AuthenticateResult authenticationResult = await policyEvaluator.AuthenticateAsync(policy, context);
 //如果允许匿名访问,则不再进行授权检查。
 if (endpoint?.Metadata.GetMetadata() != 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 CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable authorizeData)
{
	if (policyProvider == null)
	{
		throw new ArgumentNullException("policyProvider");
	}
	if (authorizeData == null)
	{
		throw new ArgumentNullException("authorizeData");
	}
	bool flag = false;
	IList list = authorizeData as IList;
 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 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 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 logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator, IOptions 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 AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable 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 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 \_pendingRequirements;

 private bool \_failCalled;

 private bool \_succeedCalled;

 public virtual IEnumerable Requirements
 {
 get;
 }

 public virtual ClaimsPrincipal User
 {
 get;
 }

 public virtual object Resource
 {
 get;
 }

 public virtual IEnumerable PendingRequirements => \_pendingRequirements;

 public virtual bool HasFailed => \_failCalled;

 public virtual bool HasSucceeded
 {
 get
 {
 if (!\_failCalled && \_succeedCalled)
 {
 return !PendingRequirements.Any();
 }
 return false;
 }
 }

 public AuthorizationHandlerContext(IEnumerable requirements, ClaimsPrincipal user, object resource)
 {
 if (requirements == null)
 {
 throw new ArgumentNullException("requirements");
 }
 Requirements = requirements;
 User = user;
 Resource = resource;
 \_pendingRequirements = new HashSet(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())
 {
 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一一进行检查,如果全部授权通过,并且没有显式调用授权失败的方法,则授权成功。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u013190417/article/details/127002877

送福利 | 送书5本《ASP.NET Core项目开发实战入门》带你走进ASP.NET Core开发-爱代码爱编程

《ASP.NET Core项目开发实战入门》从基础到实际项目开发部署带你走进ASP.NET Core开发。 ASP.NET Core项目开发实战入门是基于ASP.NET Core 3.1 所写,后续ASP.NET Core 5也会对应更新。   简介 本书共14章,深入浅出地介绍了ASP.NET Core基础及项目开发方面的知识。 主要

聊聊ASP.NET Core中的配置-爱代码爱编程

​作为软件开发人员,我们当然喜欢一些可配置选项,尤其是当它允许我们改变应用程序的行为而无需修改或编译我们的应用程序时。无论你是使用新的还是旧的.NET时,可能希望利用json文件的配置。在这篇文章中,我们将探讨读取配置所需的必要步骤以及使用这些值。 .NET的配置历史 对于那些ASP.NET老兵,你可能还记得wen.config。虽然它没有完全被抛弃,

如何在 ASP.Net Core 中使用 Autofac-爱代码爱编程

依赖注入可以有效的实现对象之间的 松耦合 并能够实现代码的可测试和可维护性,ASP.Net Core 提供了一个极简版的容器实现对 依赖注入 的原生支持,然而内置的依赖注入容器相比成熟的 依赖注入容器 太弱了。 为了解决这个问题,可以使用第三方的依赖注入容器,换句话说,你可以非常方便的使用 第三方容器 替换 原生容器, Autofac 就是这么一款

【小5聊】Asp.Net Core 2.1 主要依赖那些dll和版本-爱代码爱编程

使用vs 2019开发工具创建了asp.net core 3.1,然后又把目标框架改为了asp.net core 3.1,这个时候就出现了如下错误提示,3.1和2.1用到的类还是有很大区别 core 2.1与standard 2.0,支持最低版本的core为2.1 IApplicationBuilder Microsoft.A

【小5聊】asp.net和asp.net core不同点积累-爱代码爱编程

从asp.net framework框架切换到asp.net core框架,同样的功能,写法不一样 1、Request获取地址参数方法  1)framework Request.QueryString["echoStr"] 2)core HttpContext.Request.Query["echoStr"] 2、Response

【小5聊】Asp.Net Core 2.1基础之开启HttpContext内容请求-爱代码爱编程

Asp.Net Core 框架的目的之一就是为了跨平台,要跨平台就必须跳出依赖于Windows底层的dll,那么framework框架很多函数库就无法使用了,就必须重新使用新的方式 1、创建一个自定义助手类  代码public static class HttpContext { private static Microsoft.As

在 ASP.NET Core 中使用多种方式给 Action 传参-爱代码爱编程

ASP.NET Core 是一个跨平台,开源的,轻量级,高性能 并且 高度模块化的web框架。在 ASP.NET Core MVC 中有很多种方式可以给 Action 方法传递参数,比如说:url方式,querystring方式,request header,request body,form 等等。本篇就和大家一起讨论下如何使用这些方式,并且用代码

【小5聊】Asp.Net Core2.1基础之跨域设置-爱代码爱编程

前后端彻底分离后,特别是前端和API放在不同服务器不同域名指向时,就存在了跨域的可能,Core默认是没有设置开启跨域,需要手动添加 1、默认提示不可跨域 2、设置跨域 代码public class Startup { public Startup(IConfiguration configuration) {

【小5聊】Asp.Net Core解决返回Json时间带T的方法-爱代码爱编程

在.net framework框架里,貌似没有怎么遇到过Json返回时间会带T的情况,转到.net core框架后,发现时间会带T,比如:2022-02-25T20:36:13.32 1、Json时间带T   2、解决方法  在配置服务方法-ConfigureServices-里修改MVC组件 //添加M

ASP.NET Core的几种服务器类型[共6篇]-爱代码爱编程

Python微信订餐小程序课程视频 https://blog.csdn.net/m0_56069948/article/details/122285951 Python实战量化交易理财系统 https://blog.csdn.net/m0_56069948/article/details/122285941 作为ASP.NET CORE请求处理管道的

ASP.NET Core MVC 知识-爱代码爱编程

简介 .NET Framework类库,是微软.NET下提供的一个底层类库,封装了很多类供我们开发。 代码运行在CLR(公共运行平台)。 问题: 1.CLR和windows系统结合是非常好。在windows平台下都很流畅。 2.VisalStido开源, Framework只能运行在window操作系统。 随着微软拥抱开源,微软开发了一套全新的框

【小5聊】asp.net core 2.1基础之部署在iis上接口请求超时解决方法_访问iis接口超时-爱代码爱编程

由于项目业务需求,对于调用和请求接口A可能需要比较长的时候,比如10分钟,那么对于core默认2分钟的请求时间就不够用了  1、Core默认请求时间 默认请求时间是2分钟 2、Core在发布后自动生成web.config配置文件 web.config基础信息如下 <?xml version="1.0" encoding="utf-

为什么选择asp.net core-爱代码爱编程

什么是.NET   有一次小飞去面试,面试官上来就问了一个宏观的问题:“你觉得什么是.NET”?小飞的脑子嗡嗡的,支吾了半天,才吐了一些碎片化的词语:“跨平台、开源、微软…” 虽然作为一个.NET人,但是小飞更喜欢编码细节,对理论这一类不是很感兴趣,比如.NET历史枷锁、什么是.NET等。一个源于技术更新发展太快,另外一个原因是小飞觉得技术以

asp.net core服务限制堆内存大小_dotnet 6 限制内存-爱代码爱编程

文章目录 前言1、asp.net core是什么2、限制其堆内存最大大小2.1 设置.NET 运行时的配置2.2 在项目中创建runtimeconfig.json配置文件2.2 限制堆的大小 3、测试配置