zoukankan      html  css  js  c++  java
  • ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?

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

    目录

    1. MVC中的授权
    2. IPolicyEvaluator
    3. IAuthorizationService

    在《(上一章》中提到,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的概念,它用来封装ControllerFilterApiExplorer等。对应的,在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中,依次调到了AuthenticateAsyncAuthorizeAsync方法,我们就一一来看。

    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)

    接下来再看一下PolicyEvaluatorAuthorizeAsync方法:

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

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

    authorization_service

    总结

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

     
    好文要顶 已关注 收藏该文  
     
    推荐博客
    我在关注他 取消关注
     
    22
     
     
     
    « 上一篇: ASP.NET Core 认证与授权[5]:初识授权 
    » 下一篇: ASP.NET Core 认证与授权[7]:动态授权
  • 相关阅读:
    excel表格的jar包
    mac和jar的关系
    BAT批处理文件入门-笔记
    没看完_perl的find模块很全面的讲解!!
    perl怎么拷贝一个文件到另一个文件夹中或者怎么拷贝文件夹到另一个文件夹
    perl 中用到的-*的具体解释,方便查看
    perl语言的笔记--啥是mkpath
    剑指哦佛_我的第一篇博客,哦耶
    ajax创建万能的XmlHttpRequest对象
    动态实例化窗体
  • 原文地址:https://www.cnblogs.com/webenh/p/11579745.html
Copyright © 2011-2022 走看看