您的位置:首页 > 编程语言 > ASP

ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?

2017-11-24 00:00 1246 查看
在上一章中ASP.NET
Core 认证与授权[5]:初识授权,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上
[Authorize]
特性,并指定要执行的策略名称即可,但是,授权策略是怎么执行的呢?怀着一颗好奇的心,忍不住来探索一下它的执行流程。

在《(上一章》中提到,
AuthorizeAttribute
只是一个简单的实现了
IAuthorizeData
接口的特性,并且在
ASP.NET Core 授权系统中并没有使用到它。我们知道在认证中,还有一个
UseAuthentication
扩展方法来激活认证系统,但是在授权中并没有类似的机制。
这是因为当我们使用
[Authorize]
通常是在MVC中,由MVC来负责激活授权系统。本来在这个系列的文章中,我并不想涉及到MVC的知识,但是为了能更好的理解授权系统的执行,就来简单介绍一下MVC中与授权相关的知识。


MVC中的授权

当我们使用MVC时,首先会调用MVC的
AddMvc
扩展方法,用来注册一些MVC相关的服务:
public static IMvcBuilder AddMvc(this IServiceCollection services){    var builder = services.AddMvcCore();

   builder.AddAuthorization();

   ...
}public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder){
   AddAuthorizationServices(builder.Services);    return builder;
}internal static void AddAuthorizationServices(IServiceCollection services){
   services.AddAuthenticationCore();
   services.AddAuthorization();
   services.AddAuthorizationPolicyEvaluator();

   services.TryAddEnumerable(
       ServiceDescriptor.Transient<IApplicationModelProvider, AuthorizationApplicationModelProvider>());
}

在上面
AddAuthorizationServices
中的前三个方法都属于 ASP.NET Core 《Security》项目中提供的扩展方法,其中前两个在前面几章已经介绍过了,对于
AddAuthorizationPolicyEvaluator
放到后面再来介绍,我们先来看一下MVC中的
AuthorizationApplicationModelProvider


AuthorizationApplicationModelProvider

在MVC中有一个
ApplicationModel
的概念,它用来封装
Controller
Filter
ApiExplorer
等。对应的,在MVC中还提供了一系列的ApplicationModelProvider来初始化
ApplicationModel
的各个部分,而
AuthorizationApplicationModelProvider
就是用来初始化与授权相关的部分。
public class AuthorizationApplicationModelProvider : IApplicationModelProvider{  
 public void OnProvidersExecuting(ApplicationModelProviderContext context)    {        foreach (var controllerModel in context.Result.Controllers)
       {            var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray();            if (controllerModelAuthData.Length > 0)
           {
               controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData));
           }            foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>())
           {
               controllerModel.Filters.Add(new AllowAnonymousFilter());
           }            foreach (var actionModel in controllerModel.Actions)
           {                var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray();                if (actionModelAuthData.Length > 0)
               {
                   actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData));
               }                foreach (var attribute in actionModel.Attributes.OfType<IAllowAnonymous>())
               {
                   actionModel.Filters.Add(new AllowAnonymousFilter());
               }
           }
       }
   }
}

如上,首先查找每个Controller中实现了
IAuthorizeData
接口的特性,然后将其转化为
AuthorizeFilter
并添加到Controller的Filter集合中,紧接着再查找实现了
IAllowAnonymous
接口的特性,将其转化为
AllowAnonymousFilter
过滤器也添加到Filter集合中,然后以同样的逻辑查找Action上的特性并添加到Action的Filter集合中。
其中的关键点就是将
IAuthorizeData
(也就是通过我们熟悉的
[Authorize]
特性)转化为MVC中的
AuthorizeFilter
过滤器:
public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authData){    if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider))
   {        var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult();        return new AuthorizeFilter(policy);
   }    else
   {        return new AuthorizeFilter(policyProvider, authData);
   }
}

CombineAsync
在上一章的《AuthorizationPolicy》中已经介绍过了,我们往下看看AuthorizeFilter的实现。


AuthorizeFilter

在MVC中有一个
AuthorizeFilter
过滤器,类似我们在ASP.NET 4.x中所熟悉的
[Authorize]
,它实现了
IAsyncAuthorizationFilter
接口,定义如下:
public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory{  
 public AuthorizeFilter(AuthorizationPolicy policy) {}  
   public AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) : this(authorizeData) {}    public AuthorizeFilter(IEnumerable<IAuthorizeData> authorizeData) {}    public IEnumerable<IAuthorizeData> AuthorizeData { get; }  
     public AuthorizationPolicy Policy { get; }  
     
       public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context)    {    
          var effectivePolicy = Policy;      
           if (effectivePolicy == null)
       {
           effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData);
       }      
            var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();        var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext);        if (context.Filters.Any(item => item is IAllowAnonymousFilter))
       {            return;
       }        
            var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context);

       ... // 如果授权失败,返回ChallengeResult或ForbidResult
   }
}

AuthorizeFilter的
OnAuthorizationAsync
方法会在Action执行之前触发,其调用
IPolicyEvaluator
来完成授权,将执行流程切回到
ASP.NET Core 授权系统中。关于MVC中
IApplicationModelProvider
以及
Filter
的概念,在以后MVC系列的文章中再来详细介绍,下面就继续介绍
ASP.NET Core 的授权系统,也就是《Security》项目。


IPolicyEvaluator

IPolicyEvaluator是MVC调用授权系统的入口点,其定义如下:
public interface IPolicyEvaluator{    Task<AuthenticateResult> AuthenticateAsync(AuthorizationPolicy policy, HttpContext context);    Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource);
}

在上面介绍的
AddMVC
中,调用了
AddAuthorizationPolicyEvaluator
扩展方法,它有如下定义:
public static class PolicyServiceCollectionExtensions{  

 public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)    {
       services.TryAdd(ServiceDescriptor.Transient<IPolicyEvaluator, PolicyEvaluator>());        return services;
   }
}

由此可知
IPolicyEvaluator
的默认实现为
PolicyEvaluator
,我们就从它入手,来一步一步解剖
ASP.NET Core 授权系统的执行步骤。
AuthorizeFilter
中,依次调到了
AuthenticateAsync
AuthorizeAsync
方法,我们就一一来看。


AuthenticateAsync(AuthenticationSchemes)

为什么还有一个
AuthenticateAsync
方法呢,这不是在认证阶段执行的吗?我们看下它的实现:
public class PolicyEvaluator : IPolicyEvaluator{  

 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();
   }
}

在《上一章》中,我们知道在AuthorizationPolicy中有AuthenticationSchemes和IAuthorizationRequirement两个属性,并详细介绍介绍了Requirement,但是没有提到AuthenticationSchemes的调用。
那么,看到这里,也就大概明白了,它与Requirements的执行是完全独立的,并在它之前执行,用于重置Claims,那么为什么要重置呢?
在认证的章节介绍过,在认证阶段,只会执行默认的认证Scheme,
context.User
就是使用
context.AuthenticateAsync(DefaultAuthenticateScheme)
来赋值的,当我们希望使用非默认的Scheme,或者是想合并多个认证Scheme的Claims时,就需要使用基于Scheme的授权来重置Claims了。
它的实现也很简单,直接使用我们在授权策略中指定的Schemes来依次调用认证服务的
AuthenticateAsync
方法,并将生成的Claims合并,最后返回我们熟悉的
AuthenticateResult
认证结果。


AuthorizeAsync(Requirements)

接下来再看一下PolicyEvaluator的
AuthorizeAsync
方法:
public class PolicyEvaluator : IPolicyEvaluator{  

 private readonly IAuthorizationService _authorization;
    public PolicyEvaluator(IAuthorizationService authorization)    {
       _authorization = authorization;
   }  
    
      public virtual async Task<PolicyAuthorizationResult> AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object resource)    {        var result = await _authorization.AuthorizeAsync(context.User, resource, policy);      
        if (result.Succeeded) return PolicyAuthorizationResult.Success();  
           return (authenticationResult.Succeeded) ? PolicyAuthorizationResult.Forbid() : PolicyAuthorizationResult.Challenge();
   }
}

该方法会根据Requirements来完成授权,具体的实现是通过调用
IAuthorizationService
来实现的。
最终返回的是一个
PolicyAuthorizationResult
对象,并在授权失败时,根据认证结果来返回
Forbid(未授权)
Challenge(未登录)

public class PolicyAuthorizationResult{  
 private PolicyAuthorizationResult() { }  
  public bool Challenged { get; private set; }  
    public bool Forbidden { get; private set; }  
      public bool Succeeded { get; private set; }
}


IAuthorizationService

然后就到了授权的核心对象
AuthorizationService
,也可以称为授权的外交官,我们也可以直接在应用代码中调用该对象来实现授权,它有如下定义:
public interface IAuthorizationService{    
   Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);    Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
}


AuthorizeAsync
中还涉及到一个
resource
对象,用来实现面向资源的授权,放在下一章中再来介绍,而在本章与《前一章》的示例中,该值均为
null


ASP.NET Core 中还为
IAuthorizationService
提供了几个扩展方法:
public static class AuthorizationServiceExtensions{  
 public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, string policyName) {}  
 
  public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, AuthorizationPolicy policy) {}  
    public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, IAuthorizationRequirement requirement) {}  
     public static Task<AuthorizationResult> AuthorizeAsync(this IAuthorizationService service, ClaimsPrincipal user, object resource, AuthorizationPolicy policy) {}
}

其默认实现为
DefaultAuthorizationService
:
public class DefaultAuthorizationService : IAuthorizationService{    private readonly AuthorizationOptions _options;    private readonly IAuthorizationHandlerContextFactory _contextFactory;    private readonly IAuthorizationHandlerProvider _handlers;    private readonly IAuthorizationEvaluator _evaluator;    private readonly IAuthorizationPolicyProvider _policyProvider;    public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName)    {        
       var policy = await _policyProvider.GetPolicyAsync(policyName);        return await this.AuthorizeAsync(user, resource, policy);
   }    public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> 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;
           }
       }        return _evaluator.Evaluate(authContext);
   }
}

通过上面代码可以看出,在《上一章》中介绍的授权策略,在这里获取到它的Requirements,后续便不再需要了。而在
AuthorizationService
中是通过调用四大核心对象来完成授权,我们一一来看。


IAuthorizationPolicyProvider

由于在
[Authorize]
中,我们指定的是策略的名称,因此需要使用
IAuthorizationPolicyProvider
来根据名称获取到策略对象,默认实现为
DefaultAuthorizationPolicyProvider

public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider{  
 private readonly AuthorizationOptions _options;  
 
  public Task<AuthorizationPolicy> GetDefaultPolicyAsync()    {        return Task.FromResult(_options.DefaultPolicy);
   }  
  
    public virtual Task<AuthorizationPolicy> GetPolicyAsync(string policyName)    {        return Task.FromResult(_options.GetPolicy(policyName));
   }
}

在上一章中介绍过,我们定义的策略都保存在《AuthorizationOptions》的字典中,因此在这里只是简单的将
AuthorizationOptions
中的同名方法异步化。


IAuthorizationHandlerContextFactory

授权上下文是我们接触较多的对象,当我们自定义授权Handler时就会用到它,它是使用简单工厂模式来创建的:
public class DefaultAuthorizationHandlerContextFactory : IAuthorizationHandlerContextFactory{  

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

授权上下文中主要包含用户的Claims和授权策略的Requirements:
public class AuthorizationHandlerContext{  

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

如上,
_pendingRequirements
中保存着所有待验证的Requirements,验证成功的Requirement则从中移除。


IAuthorizationHandlerProvider

兜兜转转,终于进入到了授权的最终验证逻辑中了,首先,使用
IAuthorizationHandlerProvider
来获取到所有的授权Handler。
IAuthorizationHandlerProvider
的默认实现为
DefaultAuthorizationHandlerProvider
:
public class DefaultAuthorizationHandlerProvider : IAuthorizationHandlerProvider{  
 private readonly IEnumerable<IAuthorizationHandler> _handlers;    public DefaultAuthorizationHandlerProvider(IEnumerable<IAuthorizationHandler> handlers)    {
       _handlers = handlers;
   }    
 
 public Task<IEnumerable<IAuthorizationHandler>> GetHandlersAsync(AuthorizationHandlerContext context)
       => Task.FromResult(_handlers);
}

在《上一章》中,我们还介绍到,我们定义的Requirement,可以直接实现
IAuthorizationHandler
接口,也可以单独定义Handler,但是需要注册到DI系统中去。
在默认的AuthorizationHandlerProvider中,会从DI系统中获取到我们注册的所有Handler,最终调用其
HandleAsync
方法。
我们在实现
IAuthorizationHandler
接口时,通常是继承自
AuthorizationHandler<TRequirement>
来实现,它有如下定义:
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又是如何执行的呢?
AddAuthorization
扩展方法中可以看到,默认还为
IAuthorizationHandler
注册了一个
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。


IAuthorizationEvaluator

最后,通过调用
IAuthorizationEvaluator
接口,来完成最终的授权结果,默认实现为
DefaultAuthorizationEvaluator
:
public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator{    public AuthorizationResult Evaluate(AuthorizationHandlerContext context)        => context.HasSucceeded
           ? AuthorizationResult.Success()
           : AuthorizationResult.Failed(context.HasFailed
               ? AuthorizationFailure.ExplicitFail()
               : AuthorizationFailure.Failed(context.PendingRequirements));
}

当我们在一个策略中指定多个Requirement时,只有全部验证通过时,授权上下文中的
HasSucceeded
才会为True,而
HasFailed
代表授权结果的显式失败。
这里根据授权上下文的验证结果来生成授权结果:
public class 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()    {        return new AuthorizationFailure { FailCalled = true, FailedRequirements = new IAuthorizationRequirement[0] };
   }    public static AuthorizationFailure Failed(IEnumerable<IAuthorizationRequirement> failed)        => new AuthorizationFailure { FailedRequirements = failed };

}

整个授权流程的结构大致如下:




总结

通过对 ASP.NET Core 授权系统执行流程的探索,可以看出授权是主要是通过调用
IAuthorizationService
来完成的,而授权策略的本质是提供 Requirement ,我们完全可以使用它们两个来完成各种灵活的授权方式,而不用局限于策略。在
ASP.NET Core 中,还提供了基于资源的授权,放在下一章中来介绍,并会简单演示一下在一个通用权限管理系统中如何来授权。

相关文章:

ASP.NET
Core 认证与授权[4]:JwtBearer认证

ASP.NET
Core 认证与授权[2]:Cookie认证

ASP.NET
Core 认证与授权[3]:OAuth & OpenID Connect认证

Asp.Net
Core 2.0 多角色权限认证

asp.net
core 2.0 web api基于JWT自定义策略授权

ASP.NET
Core 认证与授权[5]:初识授权

原文:http://www.cnblogs.com/RainingNight/p/authorize-how-to-work-in-asp-net-core.html
.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: