2020年7月10日星期五

基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)

一、前言

回顾:基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (上篇)

在上一篇中,主要讲解了授权在配置方面的源码,从添加授权配置开始,我们引入了需要的授权配置选项,而不同的授权要求构建不同的策略方式,从而实现一种自己满意的授权需求配置要求。

在这一节中,继续上一篇的内容往下深入了解授权内部机制的奥秘以及是如何实现执行授权流程的。

二、说明

在上一篇中,我们通过定义授权策略,查看源码发现,在对授权配置AuthorizationOptions之后,授权系统通过DI的方式注册了几个核心的默认实现。

之前我们进行对步骤一的授权有了大概了解,所以下面我们将对步骤二进行的注册对象进行说明。

三、开始

3.1 IAuthorizationService

授权服务接口,用来确定授权是否成功的主要服务,接口的定义为

 public interface IAuthorizationService {   Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);   Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName); }

两个接口的参数不同之处在于IAuthorizationRequirementpolicyName,分别是指定资源的一组特定要求和指定的授权名称。

同时asp.net core还为IAuthorizationService 接口拓展了几个方法:

 public static class AuthorizationServiceExtensions {  public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement)  {   if (service == null)   {    throw new ArgumentNullException(nameof(service));   }   if (requirement == null)   {    throw new ArgumentNullException(nameof(requirement));   }   return service.AuthorizeAsync(user, resource, new IAuthorizationRequirement[] { requirement });  }  public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, AuthorizationPolicy policy)  {   if (service == null)   {    throw new ArgumentNullException(nameof(service));   }   if (policy == null)   {    throw new ArgumentNullException(nameof(policy));   }   return service.AuthorizeAsync(user, resource, policy.Requirements);  }  public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, AuthorizationPolicy policy)  {   if (service == null)   {    throw new ArgumentNullException(nameof(service));   }   if (policy == null)   {    throw new ArgumentNullException(nameof(policy));   }   return service.AuthorizeAsync(user, resource: null, policy: policy);  }  public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName)  {   if (service == null)   {    throw new ArgumentNullException(nameof(service));   }   if (policyName == null)   {    throw new ArgumentNullException(nameof(policyName));   }   return service.AuthorizeAsync(user, resource: null, policyName: policyName);  } }

接口的默认实现为DefaultAuthorizationService

DefaultAuthorizationService的实现主要是用来对 IAuthorizationRequirement对象的授权检验。

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(nameof(options));  }  if (policyProvider == null)  {   throw new ArgumentNullException(nameof(policyProvider));  }  if (handlers == null)  {   throw new ArgumentNullException(nameof(handlers));  }  if (logger == null)  {   throw new ArgumentNullException(nameof(logger));  }  if (contextFactory == null)  {   throw new ArgumentNullException(nameof(contextFactory));  }  if (evaluator == null)  {   throw new ArgumentNullException(nameof(evaluator));  }  _options = options.Value;  _handlers = handlers;  _policyProvider = policyProvider;  _logger = logger;  _evaluator = evaluator;  _contextFactory = contextFactory; } public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements) {  if (requirements == null)  {   throw new ArgumentNullException(nameof(requirements));  }  var authContext = _contextFactory.CreateContext(requirements, user, resource);  var handlers = await _handlers.GetHandlersAsync(authContext);  foreach (var handler in handlers)  {   await handler.HandleAsync(authContext);   if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed)   {    break;   }  }  var result = _evaluator.Evaluate(authContext);  if (result.Succeeded)  {   _logger.UserAuthorizationSucceeded();  }  else  {   _logger.UserAuthorizationFailed();  }  return result; } public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName) {  if (policyName == null)  {   throw new ArgumentNullException(nameof(policyName));  }  var policy = await _policyProvider.GetPolicyAsync(policyName);  if (policy == null)  {   throw new InvalidOperationException($"No policy found: {policyName}.");  }  return await this.AuthorizeAsync(user, resource, policy); }}

通过上面的代码可以发现,在对象实例中,通过构造函数的方式分别注入了IAuthorizationPolicyProviderIAuthorizationHandlerProviderIAuthorizationEvaluatorIAuthorizationHandlerContextFactory这几个核心服务,以及配置选项的AuthorizationOptions对象,再通过实现的方法AuthorizeAsync可以看出,在方法中调用GetPolicyAsync来获取Requirements,具体的可以看一下上一节的AuthorizationPolicy,而后在根据授权上下文来判断。

这里就用到了注入的几个核心对象来实现完成授权的。下面会分别介绍到的。

3.2 IAuthorizationPolicyProvider

由上面的IAuthorizationServer接口的默认实现可以发现,在进行授权检验的时候,DefaultAuthorizationService会利用注入的IAuthorizationPolicyProvider服务来提供注册的授权策略,所以我们查看源码发现,接口提供 了默认的授权策略GetDefaultPolicyAsync和指定名称的授权策略·GetPolicyAsync(string policyName)的方法。

public interface IAuthorizationPolicyProvider{ Task<AuthorizationPolicy> GetPolicyAsync(string policyName); Task<AuthorizationPolicy> GetDefaultPolicyAsync(); Task<AuthorizationPolicy> GetFallbackPolicyAsync();}

再加上在使用[Authorize]进行策略授权的时候,会根据提供的接口方法来获取指定的授权策略。

IAuthorizationPolicyProvider来根据名称获取到策略对象,默认实现为DefaultAuthorizationPolicyProvider

DefaultAuthorizationPolicyProvider

 public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider {  private readonly AuthorizationOptions _options;  private Task<AuthorizationPolicy> _cachedDefaultPolicy;  private Task<AuthorizationPolicy> _cachedFallbackPolicy;  public DefaultAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)  {   if (options == null)   {    throw new ArgumentNullException(nameof(options));   }   _options = options.Value;  }  public Task<AuthorizationPolicy> GetDefaultPolicyAsync()  {   return GetCachedPolicy(ref _cachedDefaultPolicy, _options.DefaultPolicy);  }  public Task<AuthorizationPolicy> GetFallbackPolicyAsync()  {   return GetCachedPolicy(ref _cachedFallbackPolicy, _options.FallbackPolicy);  }  private Task<AuthorizationPolicy> GetCachedPolicy(ref Task<AuthorizationPolicy> cachedPolicy, AuthorizationPolicy currentPolicy)  {   var local = cachedPolicy;   if (local == null || local.Result != currentPolicy)   {    cachedPolicy = local = Task.FromResult(currentPolicy);   }   return local;  }  public virtual Task<AuthorizationPolicy> GetPolicyAsync(string policyName)  {   return Task.FromResult(_options.GetPolicy(policyName));  } }

由上面的代码可以看出,在实现DefaultAuthorizationPolicyProvider对象进行构造函数的方式注入了IOptions<AuthorizationOptions> options服务来提供配置选项AuthorizationOptions(不懂的可以查看上一篇的AuthorizationOptions),再通过实现的方法可以看出是如何获取到注册的授权策略的了。附加一个图片

在上一章中介绍过,我们定义的策略都保存在AuthorizationOptions的中PolicyMap字典中,由上代码可以发现这字典的用处。

3.3 IAuthorizationHandlerContextFactory

先看看这个接口的源代码

public interface IAuthorizationHandlerContextFactory{ AuthorizationHandlerContext CreateContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource);}

接口定义了一个唯一的方法CreateContext,作用在于创建授权上下文AuthorizationHandlerContext对象。接口默认实现方式

 public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory {  public virtual AuthorizationHandlerContext CreateContext(IEnumerable<IAuthorizationRequirement> requirements, ClaimsPrincipal user, object resource)  {   return new AuthorizationHandlerContext(requirements, user, resource);  } }

再来看看AuthorizationHandlerContext授权上下文对象,可以看出,上下文中主要包括用户的Claims和授权策略的要求Requirements

public class AuthorizationHandlerContext{  private HashSet<IAuthorizationRequirement> _pendingRequirements;  private bool _failCalled;  private bool _succeedCalled; public AuthorizationHandlerContext(  IEnumerable<IAuthorizationRequirement> requirements,  ClaimsPrincipal user,  object resource) {  if (requirements == null)  {   throw new ArgumentNullException(nameof(requirements));  }  Requirements = requirements;  User = user;  Resource = resource;  _pendingRequirements = new HashSet<IAuthorizationRequirement>(requirements); } public virtual IEnumerable<IAuthorizationRequirement> Requirements { get; } public virtual ClaimsPrincipal User { get; } public virtual object Resource { get; } public virtual IEnumerable<IAuthorizationRequirement> PendingRequirements { get { return _pendingRequirements; } } public virtual bool HasFailed { get { return _failCalled; } } public virtual bool HasSucceeded {  get  {   return !_failCalled && _succeedCalled && !PendingRequirements.Any();  } } public virtual void Fail() {  _failCalled = true; } public virtual void Succeed(IAuthorizationRequirement requirement) {  _succeedCalled = true;  _pendingRequirements.Remove(requirement); }}

因此,在下面我们刚好会提到了IAuthorizationHandlerProvider 中的方法,可以根据授权上下文获取到请求调用的处理程序。

3.4 IAuthorizationHandlerProvider

这个是接口的方法,作用是获取所有的授权Handler

public interface IAuthorizationHandlerProvider{ Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context);}

根据之前提到的授权上下文作为GetHandlersAsync方法参数对象来提取IAuthorizationHandler对象。

默认接口的实现为DefaultAuthorizationHandlerProvider, 处理程序的默认实现,为授权请求提供IAuthorizationHandler

 public class DefaultAuthorizationHandlerProvider : IAuthorizationHandlerProvider {  private readonly IEnumerable<IAuthorizationHandler> _handlers;  public DefaultAuthorizationHandlerProvider(IEnumerable<IAuthorizationHandler> handlers)  {   if (handlers == null)   {    throw new ArgumentNullException(nameof(handlers));   }   _handlers = handlers;  }  public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)   => Task.FromResult(_handlers); }

从默认实现的方式可以看出,利用构造函数的方式注入默认的IAuthorizationHandler的对象,但是我们再看看接口的实现方法可以发现,GetHandlersAsync返回的IAuthorizationHandler对象并不是从给定的AuthorizationHandlerContext上下文中获取的,而是直接通过构造函数的方式注入得到的。

这个时候,你可能会问,那么IAuthorizationHandler是在哪里注入的呢?

对应下面的 IAuthorizationHandler

3.5 IAuthorizationEvaluator

DefaultAuthorizationService中的授权方法过程调用了

 var result = _evaluator.Evaluate(authContext);

IAuthorizationEvaluator接口,来确定授权结果是否成功。

 public interface IAuthorizationEvaluator {  AuthorizationResult Evaluate(AuthorizationHandlerContext context); }

IAuthorizationEvaluator的唯一方法Evaluate,该方法会根据之前提供的授权上下文返回一个表示授权成功的AuthorizationResult对象。默认实现为DefaultAuthorizationEvaluator

public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator{ public AuthorizationResult Evaluate(AuthorizationHandlerContext context)  => context.HasSucceeded   ? AuthorizationResult.Success()   : AuthorizationResult.Failed(context.HasFailed    ? AuthorizationFailure.ExplicitFail()    : AuthorizationFailure.Failed(context.PendingRequirements));}

由默认实现可以看出,AuthorizationHandlerContext对象的HasSucceeded属性决定了授权是否成功。当验证通过时,授权上下文中的HasSucceeded才会为True。

其中的AuthorizationResultAuthorizationFailure分别为

public class AuthorizationResult{  private AuthorizationResult() { } public bool Succeeded { get; private set; } public AuthorizationFailure Failure { get; private set; } public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true }; public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure }; public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() };}
public class AuthorizationFailure{ private AuthorizationFailure() { } public bool FailCalled { get; private set; } public IEnumerable<IAuthorizationRequirement> FailedRequirements { get;private set; }  public static AuthorizationFailure ExplicitFail()  => new AuthorizationFailure {  FailCalled = true,  FailedRequirements = new IAuthorizationRequirement[0] }; public static AuthorizationFailure Failed(IEnumerable<IAuthorizationRequirement> failed)  => new AuthorizationFailure { FailedRequirements = failed };}

这里的两个授权结果 正是IAuthorizationService 进行实现授权AuthorizeAsync来完成校验返回的结果。

3.6 IAuthorizationHandler

接口方式实现,判断是否授权,实现此接口的类

public interface IAuthorizationHandler{ Task HandleAsync(AuthorizationHandlerContext context);}

如果允许授权,可通过此接口的方法来决定是否允许授权。

之前我们还介绍到,我们定义的Requirement,可以直接实现IAuthorizationHandler接口,也可以单独定义Handler,但是需要注册到DI系统中去。

在默认的AuthorizationHandlerProvider中,会从DI系统中获取到我们注册的所有Handler,最终调用其HandleAsync方法。

我们在实现IAuthorizationHandler接口时,通常是继承自AuthorizationHandler来实现,它有如下定义:

public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler where TRequirement : IAuthorizationRequirement{public virtual async Task HandleAsync(AuthorizationHandlerContext context){foreach (var req in context.Requirements.OfType<TRequirement>()){ await HandleRequirementAsync(context, req);}}protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement);}

如上,首先会在HandleAsync过滤出与Requirement对匹配的Handler,然后再调用其HandleRequirementAsync方法。

那我们定义的直接实现IAuthorizationHandler了接口的Requirement又是如何执行的呢?

我们可以发现,IAuthorizationHandlerAddAuthorization拓展方法中可以看到默认注册了一个PassThroughAuthorizationHandler默认实现为:

public class PassThroughAuthorizationHandler : IAuthorizationHandler{ public async Task HandleAsync(AuthorizationHandlerContext context) {  foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>())  {   await handler.HandleAsync(context);  } }}

它负责调用该策略中所有实现了IAuthorizationHandler接口的Requirement。通过接口实现的方法可以看出,当PassThroughAuthorizationHandler对象的HandleAsync方法被执行的时候,它会从AuthroizationHanderContextRequirements属性中提取所有的IAuthoizationHandler对象,并逐个调用它们的HandleAsync方法来实施授权检验。

所以可以看到的出,PassThroughAuthorizationHandler是一个特殊并且重要的授权处理器类型,其特殊之处在于它并没有实现针对某个具体规则的授权检验,但是AuthorizationHandlerContext上下文所有的IAuthorizationHandler都是通过该对象驱动执行的。

3.7 IPolicyEvaluator

接口的方式实现,为特定需求类型调用的授权处理程序的基类

 public interface IPolicyEvaluator {  Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context);  Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource); }

定义了两个方法AuthenticateAsyncAuthorizeAsync方法

IPolicyEvaluator的默认实现为PolicyEvaluator

 public class PolicyEvaluator : IPolicyEvaluator {  private readonly IAuthorizationService _authorization;    public PolicyEvaluator(IAuthorizationService authorization)  {   _authorization = authorization;  }  public virtual async Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)  {   if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)   {    ClaimsPrincipal newPrincipal = null;    foreach (var scheme in policy.AuthenticationSchemes)    {     var result = await context.AuthenticateAsync(scheme);     if (result != null && result.Succeeded)     {      newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);     }    }    if (newPrincipal != null)    {     context.User = newPrincipal;     return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));    }    else    {     context.User = new ClaimsPrincipal(new ClaimsIdentity());     return AuthenticateResult.NoResult();    }   }   return (context.User?.Identity?.IsAuthenticated ?? false)     ? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User"))    : AuthenticateResult.NoResult();  }  public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)  {   if (policy == null)   {    throw new ArgumentNullException(nameof(policy));   }   var result = await _authorization.AuthorizeAsync(context.User, resource, policy);   if (result.Succeeded)   {    return PolicyAuthorizationResult.Success();   }   // If authentication was successful, return forbidden, otherwise challenge   return (authenticationResult.Succeeded)     ? PolicyAuthorizationResult.Forbid()     : PolicyAuthorizationResult.Challenge();  } }

授权中间件委托它来实现身份验证和授权处理,它内部会调用AuthorizationService,进而执行所有授权处理器AuthorizationHandler, (在后面会提到授权中间件用到这两个方法)

3.7.1、AuthenticateAsync

当授权策略没有设置AuthenticationSchemes,则只判断下当前请求是否已做身份验证,若做了就返回成功
当授权策略设置了AuthenticationSchemes,则遍历身份验证方案逐个进行身份验证处理 。

其中context.User就是使用context.AuthenticateAsync(DefaultAuthenticateScheme)来赋值的,将所有得到的用户标识重组成一个复合的用户标识。

当我们希望使用非默认的Scheme,或者是想合并多个认证Scheme的Claims时,就需要使用基于Scheme的授权来重置Claims了。

它的实现也很简单,直接使用我们在授权策略中指定的Schemes来依次调用认证服务的AuthenticateAsync方法,并将生成的Claims合并,最后返回我们熟悉的AuthenticateResult认证结果。

3.7.2、AuthorizeAsync

该方法会根据Requirements来完成授权,具体的实现是通过调用IAuthorizationService调用AuthorizeAsync来实现的。

最终返回的是一个PolicyAuthorizationResult对象,并在授权失败时,根据认证结果来返回Forbid(未授权)Challenge(未登录)


以上汇总

  1. 授权服务IAuthorizationService,接口的默认实现为DefaultAuthorizationService,进行授权验证。
  2. 在会根据授权策略提供器IAuthorizationPolicyProvider来获取指定名称的授权。
  3. 通过授权处理器上下文对象工厂IAuthorizationHandlerContextFactory授权处理器AuthorizationHandler在授权时需要传入AuthorizationHandlerContext(上面说了授权完成后的结果也存储在里面)。所以在执行授权处理器之前需要构建这个上下文对象,就是通过这个工厂构建的,主要的数据来源就是 当前 或者 指定的 授权策略AuthorizationPolicy。
  4. 所以这个时候会授权处理提供其 IAuthorizationHandlerProvider,来获取系统中所有授权处理器。
  5. 授权评估器IAuthorizationEvaluator来确定授权结果是否成功,在授权处理器AuthorizationHandler在执行完授权后,结果是存储在AuthorizationHandlerContext中的,这里的评估器只是根据AuthorizationHandlerContext创建一个授权结果AuthorizationResult。
  6. 上面所说的授权处理器就是IAuthorizationHandler,处理器中包含主要的授权逻辑,在处理的过程中会将所有的授权处理器一一验证。
  7. 所以在授权中间件中会利用IPolicyEvaluator中实现的身份认证和授权处理方法来调用AuthorizationService来执行所有的处理器。

四、中间件

在Configure中注册管道:运行使用调用方法来配置Http请求管道

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { app.UseRouting();  //开启认证授权   app.UseAuthentication();  app.UseAuthorization(); }

在这里使用了授权中间件来检查授权,来看看中间件的源码AuthorizationMiddleware

public class AuthorizationMiddleware{ // Property key is used by Endpoint routing to determine if Authorization has run 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(nameof(next));  _policyProvider = policyProvider ?? throw new ArgumentNullException(nameof(policyProvider)); } public async Task Invoke(HttpContext context) {  if (context == null)  {   throw new ArgumentNullException(nameof(context));  }  var endpoint = context.GetEndpoint();  if (endpoint != null)  {   context.Items[AuthorizationMiddlewareInvokedWithEndpointKey] = AuthorizationMiddlewareWithEndpointInvokedValue;  }  var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();  var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);  if (policy == null)  {   await _next(context);   return;  }  var policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();  var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context);  // Allow Anonymous skips all authorization  if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)  {   await _next(context);   return;  }  // Note that the resource will be null if there is no matched endpoint  var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: endpoint);  if (authorizeResult.Challenged)  {   if (policy.AuthenticationSchemes.Any())   {    foreach (var scheme in policy.AuthenticationSchemes)    {     await context.ChallengeAsync(scheme);    }   }   else   {    await context.ChallengeAsync();   }   return;  }  else if (authorizeResult.Forbidden)  {   if (policy.AuthenticationSchemes.Any())   {    foreach (var scheme in policy.AuthenticationSchemes)    {     await context.ForbidAsync(scheme);    }   }   else   {    await context.ForbidAsync();   }   return;  }  await _next(context); }}

进行代码分解:

  1. 拿到当前请求的的终结点
 var endpoint = context.GetEndpoint();
  1. 在当前请求拿到终结点endpoint的时候,会通过终结点拿到关联的IAuthorizeData集合
var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
  1. 将根据IAuthorizeData集合调用AuthorizationPolicy.CombineAsync()来创建组合策略(具体了可以看一下上一章) ( 用例: [Authorize(Policy = "BaseRole")]
var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
  1. IPolicyEvaluator获取策略评估器对得到的组合策略进行身份验证,多种身份验证得到的用户证件信息会合并进HttpContext.User
 var policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>(); var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, context);
  1. 当使用[AllowAnonymous]的时候,则直接跳过授权检验。
 if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null) {  await _next(context);  return; }
  1. IPolicyEvaluator提供的AuthorizeAsync授权检查方法,进行策略授权检查。
 var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource: endpoint);
  1. 当进行授权时,遍历策略所有的身份验证方案,进行质询,若策略里木有身份验证方案则使用默认身份验证方案进行质询。

当授权评估拒绝就直接调用身份验证方案进行拒绝。

 if (authorizeResult.Challenged) {  if (policy.AuthenticationSchemes.Any())  {   foreach (var scheme in policy.AuthenticationSchemes)   {    await context.ChallengeAsync(scheme);   }  }  else  {   await context.ChallengeAsync();  }  return; }else if (authorizeResult.Forbidden){ if (policy.AuthenticationSchemes.Any()) {  foreach (var scheme in policy.AuthenticationSchemes)  {   await context.ForbidAsync(scheme);  } } else {  await context.ForbidAsync(); } return;}

整个过程中,授权中间件会调用授权服务IAuthorizationService来进行授权处理

五、总结

  1. 通过对上述的处理流程的分析,可以看出授权主要是通过IAuthorizationService来实现的,而我们进行使用只需要提供授权策略的Requirement,非常方便灵活的使用。
  2. 从源码权限设计来看,系统注册了各种服务,实现多种默认服务,加上默认的处理方式也满足了大部分应用需求, 所以可以看出这一块的功能还是很强大的,就算我们想通过自定义的方式来实现,也可以通过某些接口来实现拓展。
  3. 其中有很多核心源码怕说的不够清楚,所以在平时的开发项目中,再去看官方文档或源码这样理解应该更容易。
  4. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
  5. 参考的文档 和官方源码
基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)mile拍拍网服装杨颜嚣张!亚马逊公然挖墙脚,非法偷猎eBay平台卖家!Flipkart再发力,与亚马逊一较高下!别光顾看世界杯,亚马逊连出3个重磅新消息!珠海长隆私人定制旅游珠海长隆私人定制旅游珠海长隆特价旅游团报价

没有评论:

发表评论