在上一篇 聊聊 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一一进行检查,如果全部授权通过,并且没有显式调用授权失败的方法,则授权成功。