asp.net core项目授权流程详解

在上一篇 聊聊 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授权流程的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

赞 (0)

相关推荐

  • ASP.NET Core 6.0 添加 JWT 认证和授权功能

    目录 序言 相关名词 认证(Authentication) 基本步骤 1 安装 Nuget 包 2 准备配置信息 3 添加服务 4 调用中间件 5 JwtHelper 类实现 6 控制器配置 7 测试调用 授权(Authorization) 相关标签(Attribute) 授权方式 1 Policy(策略) 2 Role(角色) 3 Scheme(方案) 3 定义权限项 4 实现 Requirement 5 实现授权处理程序 Handler 6 添加授权处理程序 7 添加授权策略 8 控制器配置

  • ASP.NET Core中的策略授权和ABP授权

    目录 ASP.NETCore中的策略授权 策略 定义一个Controller 设定权限 定义策略 存储用户信息 标记访问权限 认证:Token凭据 颁发登录凭据 自定义授权 IAuthorizationService ABP授权 创建ABP应用 定义权限 ASP.NET Core 中的策略授权 首先我们来创建一个 WebAPI 应用. 然后引入 Microsoft.AspNetCore.Authentication.JwtBearer 包. 策略 Startup 类的 ConfigureServ

  • ASP.NET Core使用JWT自定义角色并实现策略授权需要的接口

    ① 存储角色/用户所能访问的 API 例如 使用 List<ApiPermission> 存储角色的授权 API 列表. 可有可无. 可以把授权访问的 API 存放到 Token 中,Token 也可以只存放角色信息和用户身份信息. /// <summary> /// API /// </summary> public class ApiPermission { /// <summary> /// API名称 /// </summary> pub

  • ASP.NET Core使用JWT认证授权的方法

    demo地址: https://github.com/william0705/JWTS 名词解析 认证 : 识别用户是否合法 授权: 赋予用户权限 (能访问哪些资源) 鉴权: 鉴定权限是否合法 Jwt优势与劣势 优势 1. 无状态 token 存储身份验证所有信息 , 服务端不需要保存用户身份验证信息, 减少服务端压力 , 服务端更容易水平扩展, 由于无状态, 又会导致它最大缺点 , 很难注销 2. 支持跨域访问 Cookie是不允许垮域访问的,token支持 3. 跨语言 基于标准化的 JSO

  • ASP.NET Core按用户等级授权的方法

    验证和授权是两个独立但又存在联系的过程.验证是检查访问者的合法性,授权是校验访问者有没有权限查看资源.它们之间的联系——先验证再授权. 贯穿这两过程的是叫 Claim 的东东,可以叫它“声明”.没什么神秘的,就是由两个字符串组成的对象,一曰 type,一曰 value.type 和 value 有着映射关系,类似字典结构的 key 和 value.Claim 用来收集用户相关信息,比如 UserName = admin Age = 105 Birth = 1990,4,12 Address =

  • 浅谈ASP.NET Core 中jwt授权认证的流程原理

    1,快速实现授权验证 什么是 JWT ?为什么要用 JWT ?JWT 的组成? 这些百度可以直接找到,这里不再赘述. 实际上,只需要知道 JWT 认证模式是使用一段 Token 作为认证依据的手段. 我们看一下 Postman 设置 Token 的位置. 那么,如何使用 C# 的 HttpClient 访问一个 JWT 认证的 WebAPI 呢? 下面来创建一个 ASP.NET Core 项目,尝试添加 JWT 验证功能. 1.1 添加 JWT 服务配置 在 Startup.cs 的 Confi

  • C#利用ASP.NET Core开发学生管理系统详解

    目录 涉及知识点 创建项目 登录模块 1. 创建控制器--LoginController 2. 创建登录视图 3. 创建用户模型 4. 创建数据库操作DataContext 5. 创建数据库和表并构造数据 6. 添加数据库连接配置 7. 添加注入信息 8. 运行测试 随着技术的进步,跨平台开发已经成为了标配,在此大背景下,ASP.NET Core也应运而生.本文主要利用ASP.NET Core开发一个学生管理系统为例,简述ASP.NET Core开发的常见知识点,仅供学习分享使用,如有不足之处,

  • ASP.NET Core中的配置详解

    ASP.NET Core 提供了一个灵活可扩展,基于键值的配置系统. 但是配置系统独立于ASP.NET Core是Microsoft.Extensions 类库的部分. 它可以用于任何类型的应用程序 1.以键-值对的形式读取配置 appsettings.json 文件: { "Position": { "Title": "编辑器", "Name": "Joe Smith" }, "MyKey&qu

  • 如何为asp.net core添加protobuf支持详解

    前言 在一些性能要求很高的应用中,使用protocol buffer序列化,优于Json.而且protocol buffer向后兼容的能力比较好. 由于Asp.net core 采用了全新的MiddleWare方式,因此使用protobuf序列化,只需要使用Protobuf-net修饰需要序列化的对象,并在MVC初始化的时候增加相应的Formatter就可以了. 没时间解释了,快上车. 通过NuGet获取Zaabee.AspNetCoreProtobuf Install-Package Zaab

  • ASP.NET Core Razor 页面路由详解

    在服务器端 Web 应用程序框架中,其中非常重要的设计是开发人员如何将URL与服务器上的资源进行匹配,以便正确的处理请求.最简单的方法是将 URL 映射到磁盘上的物理文件,在 Razor 页面框架中,ASP.NET团队就是这样实现的. 关于 Razor 页面框架如何将 URL 与文件相匹配,有一些规则您必须了解,以及如何根据需要自定义规则改变输出的结果.如果您将 Razor 页面与 Web Form 框架进行比较,您还需要了解取代的 Ur l参数以及在URL中传递数据的机制. 规则一,Razor

  • vue cli实现项目登陆页面流程详解

    目录 1. 搭建项目 1.1 使用vue-cli创建项目 1.2 通过npm安装element-ui 1.3 导入组件 2 创建登录页面 2.1 创建登录组件 2.2 引入css(css.txt) 2.3 配置路由 2.4 在Login组件中将提交按键调整为100%宽度 2.5 运行效果 3. 后台交互 3.1 引入axios 3.2 axios/qs/vue-axios安装与使用 3.2.1 安装axios 3.2.2 发送get请求 3.2.3 发送post请求 3.2.4 简化axios使

  • Python PyQt5实战项目之查询器的实现流程详解

    目录 简介 主界面 添加数据的界面 删除数据的界面 修改数据的界面 全部代码 展示 简介 这是一个操作数据库(sqlite3)的项目,用PyQt5进行界面封装. 此次项目最主要的是,主界面与子界面的交互,一个主界面与三个子界面交互 可以添加数据,修改数据,删除数据 主界面 class MainWidget(QMainWindow): def __init__(self): super(MainWidget,self).__init__() self.ui_init() self.connect_

  • SpringBoot整合Dozer映射框架流程详解

    目录 1. Dozer 介绍 2. 为什么要使用映射框架 Dozer 3. Dozer 映射框架的使用 1. Dozer 介绍 Dozer 是一个 Java Bean 到 Java Bean 的映射器,它递归地将数据从一个对象复制到另一个对象.Dozer 是用来对两个对象之间属性转换的工具,有了这个工具之后,我们将一个对象的所有属性值转给另一个对象时,就不需要再去写重复的调用 set 和 get 方法. 最重要的是,Dozer 可以确保来自数据库的内部域对象不会渗入外部表示层或外部消费者,它还可

  • react电商商品列表的实现流程详解

    目录 整体页面效果 项目技术点 拦截器的配置 主页面 添加商品 分页与搜索 修改商品 删除商品 完整代码 整体页面效果 项目技术点 antd组件库,@ant-design/icons antd的图标库 axios 接口请求,拦截器配置 node-sass sass-loader css样式的一个嵌套 react-router-dom react路由使用 react-redux redux hooks:大多数我们用的是函数组件,函数组件没有state属性,所以我们使用hooks来初始化数据,并且函

  • SpringBoot响应处理实现流程详解

    目录 1.相关依赖 2.ReturnValueHandlers—返回值处理器 3.HttpMessageConvert—消息转换器 4.开启浏览器参数方式内容协商功能 1.相关依赖 web项目引入的启动器spring-boot-starter-web中含有 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-json</arti

  • Vue二次封装axios流程详解

    目录 一.为什么要封装axios 二.怎么封装axios 三.具体步骤 vue项目的前期配置 配置config文件中的代理地址 封装axios实例-request.js 四.封装请求-http.js 五.正式封装API用于发送请求-api.js 六.如何在vue文件中调用 一.为什么要封装axios api统一管理,不管接口有多少,所有的接口都可以非常清晰,容易维护. 通常我们的项目会越做越大,页面也会越来越多,如果页面非常的少,直接用axios也没有什么大的影响,那页面组件多了起来,上百个接口

随机推荐