在上一篇 聊聊 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
  • 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接口的类)统一合并到一个策略对象中去,对于未指定,同时也把这些特性中指定的认证方案进行统一合并到一个策略对象中去,然后对当前用户对合并后的策略中所包含的认证方案一一进行身份认证,并将身份认证结果进行一一合并,然后就是对合并后的授权策略中的Requirement一一进行检查,如果全部授权通过,并且没有显式调用授权失败的方法,则授权成功。

聊聊asp.net core 授权流程的更多相关文章

  1. 一张图理清ASP.NET Core启动流程

    1. 引言 对于ASP.NET Core应用程序来说,我们要记住非常重要的一点是:其本质上是一个独立的控制台应用,它并不是必需在IIS内部托管且并不需要IIS来启动运行(而这正是ASP.NET Cor ...

  2. 探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs

    原文:探索ASP.Net Core 3.0系列二:聊聊ASP.Net Core 3.0 中的Startup.cs 前言:.NET Core 3.0 SDK包含比以前版本更多的现成模板. 在本文中,我将 ...

  3. ASP.NET Core启动流程

    1. 引言 对于ASP.NET Core应用程序来说,我们要记住非常重要的一点是:其本质上是一个独立的控制台应用,它并不是必需在IIS内部托管且并不需要IIS来启动运行(而这正是ASP.NET Cor ...

  4. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

  5. Asp.net Core 启动流程分析

    新建的.net core 程序启动本质上是一个控制台应用程序,所以它的入口在Main方法中,所以启动的开始时从Main方法开始. public class Program { public stati ...

  6. Asp.net Core启动流程讲解(四)

    Asp.net Core内 DI(DependencyInjection)贯穿了项目的始终,要学习Asp.net Core就无法越过DI. 下面讲解一下DI在Asp.Net Core内的流程 asp. ...

  7. 理解ASP.NET Core - 授权(Authorization)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 之前,我们已经了解了ASP.NET Core中的身份认证,现在,我们来聊一下授权. 老规矩,示 ...

  8. 聊聊ASP.NET Core默认提供的这个跨平台的服务器——KestrelServer

    跨平台是ASP.NET Core一个显著的特性,而KestrelServer是目前微软推出了唯一一个能够真正跨平台的Server.KestrelServer利用一个名为KestrelEngine的网络 ...

  9. 聊聊ASP.NET Core中的配置

    ​作为软件开发人员,我们当然喜欢一些可配置选项,尤其是当它允许我们改变应用程序的行为而无需修改或编译我们的应用程序时.无论你是使用新的还是旧的.NET时,可能希望利用json文件的配置.在这篇文章中, ...

  10. Asp.net core 启动流程

随机推荐

  1. C#封装程序集自定义类方法注释提示

    一.为什么使用封装程序集: 在很多分布式应用程序开发中,针对每一种功能可能条用的接口不一样,往往习惯将需要被调用的接口,封装成DLL给调用方应用后使用,这样既规范了调用的方式,又避免了调用出现参数请求 ...

  2. echarts在.Net中使用实例(一) 简单的Demo

    前言 这个必须要有前言,即便很短,对于有强迫症的人来说不容易啊.言归正传,之前做图一直使用rdlc自带的格式,虽然任务完成,但是一直觉得不太美观, 空余时间开始找其他的插件,终于找到了Highchar ...

  3. 让你忘记 Flash 的15款精彩 HTML5 游戏

    HTML5 游戏开发是一个热门的话题,开发人员和设计人员最近经常谈论到.虽然不能迅速取代 Flash 的地位,但是 HTML5 凭借它的开放性和强大的编程能力,取代 Flash 是必然的趋势.你会看到 ...

  4. ReactNative运行提示缺少文件xxxRootView.h解决方法

    我们经常在github获取源码后运行会出现缺少 "RCTRootView.h" notfound" 文件的错误,对于这种错误我们怎么解决了. 1.cd到项目根目录 删除项 ...

  5. zoj 3795 Grouping tarjan缩点 + DGA上的最长路

    Time Limit:2000MS     Memory Limit:65536KB     64bit IO Format:%lld & %llu Submit Status Practic ...

  6. Linux中printf格式化输出

    printf使用文本或者由空格分隔的参数,我们可以在printf中使用格式化字符串.printf不会写像echo那样自动添加换行符,必须手动添加 =========================== ...

  7. 一个word合并项目的分布式架构设计

    一个word合并项目的分布式架构设计 项目背景与问题起源 我们要给一个客户做word生成报告以及报告合并的工作,要合并的报告非常多,而且每个报告也比较大,一个多的报告大概有200页以上.我们用c#操作 ...

  8. tail命令

    tail命令用来取文件后几行,默认显示后10行.有多个FILE,每个都带有一个头文件名称. 语法: tail [OPTION]... [FILE]... 选项: -n#:取文件后#行,n可省略: -c ...

  9. CCF-201412-2-Z字形扫描

    问题描述 试题编号: 201412-2 试题名称: Z字形扫描 时间限制: 2.0s 内存限制: 256.0MB 问题描述: 问题描述 在图像编码的算法中,需要将一个给定的方形矩阵进行Z字形扫描(Zi ...

  10. Qt5获取本机网络信息

    获取本机网络信息 在pro文件中加入如下代码 QT += network widget.h中的代码如下 #ifndef WIDGET_H #define WIDGET_H #include <Q ...