【ASP.NET Core 授权】Authorization概念

概述

授权就是某人针对某个资源可以做什么操作
某人:登录系统的用户
资源:可以任何形式的资源,比如订单、产品、页面...。权限判断中不一定需要资源的参与,比如只要用户登录,就允许使用系统中所有功能。此时整个系统就是资源,允许所有操作。
操作:比如查看、审核、新增、修改...
dotnet core 里提供了授权框架,允许开发者编写自己的授权规则,

核心概念图

IAuthorizationRequirement(授权依据)

试想这样一种权限需求:要求属于角色"经理"或"系统管理员"的用户可以访问系统任何功能。当我们做权限判断时我们可以从HttpContext.User得到当前用户,从其证件列表中总能找到当前用户的所属角色,那么这里需要进行比较的两个角色"经理"、"系统管理员"从哪里获得呢?
再比如:要求只要当前用户的证件中包含一个"特别通行证"的Calim,就允许他访问系统的任何功能。同上面的情况一样,在判断权限时我们可以知道当前登录用户的Calim列表,那需要进行比对的"特别通行证"这个字符串从哪来呢?
asp.net core将这种权限判断时需要用来比对的数据定义为IAuthorizationRequirement,我这里叫做"授权依据",在一次权限判断中可能会存在多个判断,所以可能需要多个授权依据,文件后面会讲如何定制授权依据
其实某种意义上说“当前用户(及其包含的Calim列表)”也可以看做是一种依据,因为它也是在授权判断过程中需要访问的数据,但是这个我们是直接通过HttpContext.User来获取的,不需要我们来定义。
当我们针对某个页面或Action进行授权时可以直接从当前路由数据中获取Action名,在asp.net core 3.x中甚至更方便,可以在请求管道的早期就能获得当前请求的终结点。所以针对Action的访问也不需要定义成授权依据中

下面列出asp.net core中已提供的几种授权依据:

ClaimsAuthorizationRequirement

public string ClaimType { get; }
public IEnumerable<string> AllowedValues { get; }

将来在权限判断时会判断当前用户的Claim列表中是否包含一个类型为ClaimType的Claim,进一步判断是否完整包含AllowedValues中定义的值

DenyAnonymousAuthorizationRequirement

权限判断发现存在这个依据,则直接拒绝匿名用户访问

RolesAuthorizationRequirement

public IEnumerable<string> AllowedRoles { get; }

判断当前用户是否属于这里允许的角色中的一种

OperationAuthorizationRequirement

public string Name { get; set; }

在做功能授权时比较常用,Name代表当前操作名,比如“Order.Add”就是新增订单,将来权限判断是可以根据当前用户Id、所属角色和"Order.Add"到数据库去做对比

AssertionRequirement

public Func<AuthorizationHandlerContext, Task<bool>> Handler { get; }

这个就更强大了,直接调用这个委托来进行权限判断,所以灵活性非常大

AuthorizationPolicy(授权策略)

策略身份验证方案授权依据的容器,它包含本次授权需要的数据。
请求抵达时asp.net core会找到默认身份验证方案进行身份验证(根据请求获取用户ClaimsPrincipal),但有时候我们希望由自己来指定本次授权使用哪些身份验证验证方案,而不是使用默认的,这样将来身份验证过程中会调用设置的这几个身份验证方案去获得多张证件,此时HttpContext.User中就包含多张证件。所以授权策略里包含多种身份验证方案。
一次授权可能需要多种判断,比如同时判断所属角色、并且是否包含哪种类型的Calim并且.....,某些判断可能需要对比“授权依据”,所以一个授权策略包含多个授权依据。
另外我们可以将多个授权策略合并成一个,所有的身份验证方案合并,所有的“授权依据”合并
将来授权检查时将根据身份验证方案获取当前用户的多个证件,然后逐个判断授权依据,若都满足则认为授权检查成功。
在asp.net core 3.x中启动阶段我们可以定义一个授权策略列表,这个看成是全局授权策略,一直存在于应用中。
在应用运行时,每次进行授权时会动态创建一个授权策略,这个策略是最终进行本次授权检查用的,它可能会引用某一个或多个全局策略,所谓的引用就是合并其“身份验证方案”列表和“授权依据列表”,当然其自身的“身份验证方案”列表和“授权依据列表 ”也是可以定制的,待会在AuthorizeAttribute部分再详细说
AuthorizationPolicy源码:

public class AuthorizationPolicy
{
    public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes) {}
    public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
    public IReadOnlyList<string> AuthenticationSchemes { get; }

    public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies) 
    {
        return Combine((IEnumerable<AuthorizationPolicy>)policies);
    }
    public static AuthorizationPolicy Combine(IEnumerable<AuthorizationPolicy> policies) 
    {
        foreach (var policy in policies)
        {
            builder.Combine(policy);
        }
        return builder.Build();
    }
    public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData) 
    {
        foreach (var authorizeDatum in authorizeData)
        {
            any = true;
            var useDefaultPolicy = true;
            if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
            {
                policyBuilder.Combine(await policyProvider.GetPolicyAsync(authorizeDatum.Policy));
                useDefaultPolicy = false;
            }
            var rolesSplit = authorizeDatum.Roles?.Split(',');
            if (rolesSplit != null && rolesSplit.Any())
            {
                policyBuilder.RequireRole(rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()));
                useDefaultPolicy = false;
            }
            var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
            if (authTypesSplit != null && authTypesSplit.Any())
            {
                foreach (var authType in authTypesSplit)
                {
                    if (!string.IsNullOrWhiteSpace(authType))
                    {
                        policyBuilder.AuthenticationSchemes.Add(authType.Trim());
                    }
                }
            }
            if (useDefaultPolicy)
            {
                policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
            }
        }
        return any ? policyBuilder.Build() : null;
    }
}

AuthorizeAttribute : IAuthorizeData(定制授权依据)

在asp.net core 3.x中 启动阶段可以配置一个全局策略列表,它一直存在于系统中,暂时称为“静态策略列表”
在每次执行授权检查时也需要一个策略,暂时称为“运行时授权策略”,授权中间件执行时就会创建此策略,然后调用AuthorizationService根据此策略进行权限判断,那此策略中的“授权依据”和“身份验证方案”这俩列表从哪来的呢?就是在Action通过AuthorizeAttribute来定制的,它实现 IAuthorizeData接口

public interface IAuthorizeData
{
    string Policy { get; set; }//直接指定此Action将来授权验证时要使用的授权策略AuthorizationPolicy,此策略会被合并到当前授权策略
    string Roles { get; set; } //它会创建一个基于角色的授权依据RolesAuthorizationRequirement,此依据会被放入当前授权策略
    string AuthenticationSchemes { get; set; }//它用来定义当前授权策略里要使用哪些身份验证方案
}

Policy属性指明从“静态策略列表”拿到指定策略,然后将其“授权依据”和“身份验证方案”这俩列表合并到“运行时授权策略”

AuthorizationPolicyBuilder(策略构造器)

主要用来帮助创建一个授权策略(.net中典型的Builder模式),使用步骤是:

  • new一个AuthorizationPolicyBuilder
  • 调用各种方法对策略进行配置
  • 最后调用Build方法生成最终的授权策略。
    下面用伪代码感受下
var builder = new AuthorizationPolicyBuilder();
builder.RequireRole("Manager","Admin");
//builder....继续配置
var authorizationPolicy = builder.Build();

RequireRole将为最终会生成的策略中的“授权依据”列表加入一个RolesAuthorizationRequirement("Manager","Admin")。其它类似的api就不介绍了。

AuthorizationPolicyBuilder源码

点击查看代码
 public class AuthorizationPolicyBuilder
    {
        public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
        {
            AddAuthenticationSchemes(authenticationSchemes);
        }

        public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
        {
            Combine(policy);
        }

        public IList<IAuthorizationRequirement> Requirements { get; set; } = new List<IAuthorizationRequirement>();

        public IList<string> AuthenticationSchemes { get; set; } = new List<string>();

        public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes)
        {
            foreach (var authType in schemes)
            {
                AuthenticationSchemes.Add(authType);
            }
            return this;
        }

        public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements)
        {
            foreach (var req in requirements)
            {
                Requirements.Add(req);
            }
            return this;
        }

        public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
        {
            if (policy == null)
            {
                throw new ArgumentNullException(nameof(policy));
            }

            AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray());
            AddRequirements(policy.Requirements.ToArray());
            return this;
        }

        public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues)
        {
            if (claimType == null)
            {
                throw new ArgumentNullException(nameof(claimType));
            }

            return RequireClaim(claimType, (IEnumerable<string>)allowedValues);
        }

        public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> allowedValues)
        {
            if (claimType == null)
            {
                throw new ArgumentNullException(nameof(claimType));
            }

            Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues));
            return this;
        }

        public AuthorizationPolicyBuilder RequireClaim(string claimType)
        {
            if (claimType == null)
            {
                throw new ArgumentNullException(nameof(claimType));
            }

            Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues: null));
            return this;
        }

        public AuthorizationPolicyBuilder RequireRole(params string[] roles)
        {
            if (roles == null)
            {
                throw new ArgumentNullException(nameof(roles));
            }

            return RequireRole((IEnumerable<string>)roles);
        }

        public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> roles)
        {
            if (roles == null)
            {
                throw new ArgumentNullException(nameof(roles));
            }

            Requirements.Add(new RolesAuthorizationRequirement(roles));
            return this;
        }

        public AuthorizationPolicyBuilder RequireUserName(string userName)
        {
            if (userName == null)
            {
                throw new ArgumentNullException(nameof(userName));
            }

            Requirements.Add(new NameAuthorizationRequirement(userName));
            return this;
        }

        public AuthorizationPolicyBuilder RequireAuthenticatedUser()
        {
            Requirements.Add(new DenyAnonymousAuthorizationRequirement());
            return this;
        }

        public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, bool> handler)
        {
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }

            Requirements.Add(new AssertionRequirement(handler));
            return this;
        }

        public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, Task<bool>> handler)
        {
            if (handler == null)
            {
                throw new ArgumentNullException(nameof(handler));
            }

            Requirements.Add(new AssertionRequirement(handler));
            return this;
        }

        public AuthorizationPolicy Build()
        {
            return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
        }
    }

AuthorizationPolicyBuilder使用案例

使用AuthorizationPolicyBuilder在策略定义中组合我们需要的Requirement:

public void ConfigureServices(IServiceCollection services)
{
    var commonPolicy = new AuthorizationPolicyBuilder().RequireClaim("MyType").Build();

    services.AddAuthorization(options =>
    {
        options.AddPolicy("User", policy => policy
            .RequireAssertion(context => context.User.HasClaim(c => (c.Type == "EmployeeNumber" || c.Type == "Role")))
        );

        options.AddPolicy("Employee", policy => policy
            .RequireRole("Admin")
            .RequireUserName("Alice")
            .RequireClaim("EmployeeNumber")
            .Combine(commonPolicy));
    });
}

AuthorizationHandler(授权处理器)

上面说的当前用户、授权依据、以及授权时传递进来的资源都是可以看成是静态的数据,作为授权判断的依据,真正授权的逻辑就是用IAuthorizationHandler来表示的,先看看它的定义

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

AuthorizationHandlerContext

AuthorizationHandlerContext中包含当前用户、授权依据列表和参与授权判断的资源,前者是根据授权策略中的多个身份验证方案经过身份验证后得到的;后者就是授权策略中的授权依据列表。在方法内部处理成功或失败的结果是直接存储到context对象上的。
一个应用中可以存在多个AuthorizationHandler,在执行授权检查时逐个调用它们进行检查,若都成功则本次授权成功。

AuthorizationHandler

针对特定授权依据类型的授权处理器
上面聊过授权依据是有多种类型的,将来还可能自定义,通常授权依据不同,授权的判断逻辑也不同。
比如RolesAuthorizationRequirement这个授权依据,它里面包含角色列表,授权判断逻辑应该是判断当前用户是否属于这里面的角色;
再比如OperationAuthorizationRequirement它里面定义了操作的名称,所以授权判断逻辑应该是拿当前用户以及它所属角色和这个操作(比如是“新增”)拿到数据库去做对比
所以这样看来一种“授权依据”类型应该对应一种“授权处理器”,所以微软定义了public abstract class AuthorizationHandler<TRequirement> : IAuthorizationHandler ,这个TRequirement就代表这个授权处理器类型是针对哪种类型的“授权依据的”

一个授权策略AuthorizationPolicy是包含多个“授权依据”的,这其中可能有几个“授权依据”的类型是一样的,只是里面存储的值不同,以OperationAuthorizationRequirement为例,一个授权策略里可能包含如下授权依据列表:

new OperationAuthorizationRequirement{ Name="新增" }
new OperationAuthorizationRequirement{ Name="审核" }
new RolesAuthorizationRequirement("Manager","Admin");
//其它。。。

所以一个授权处理器AuthorizationHandler虽然只关联一种类型“授权依据”,但是一个授权处理器实例可以处理多个相同类型的“授权依据”

在授权过程中,每个AuthorizationHandler会找到自己能处理的“授权依据”,逐个进行检查

AuthorizationHandler<TRequirement, TResource>

针对特定授权依据类型、特定类型的资源的授权处理器
跟AuthorizationHandler定义及处理逻辑唯一的区别是多了个TResource,在授权过程中是可以对给定资源进行判断的,资源在AuthorizationHandlerContext.Resource,这个是object类型,为了方便子类降重重写,所以由这里的父类将AuthorizationHandlerContext.Resource转换为TResource

点击查看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);
}

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

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, TResource resource);
}

何时实施授权检查?

身份验证中间件执行后可以通过HttpContext.User获取当前用户,此时有了访问的页面和当前用户 就可以做权限判断了
请求 > 其它中间件 > 路由中间件(这里就拿到终结点了) > 身份验证中间件 > 授权中间件 > MVC中间件 > Controller > Action
还有一种情况是在业务代码内部去执行权限判断,比如:希望销售订单金额大于10000的,必须要经理角色才可以审核,此时因为我们要先获取订单才知道它的金额,所以我们最好在Action执行内部根据路由拿到订单号,去数据库查询订单金额后,调用某个方法执行权限判断。

定制授权依据AuthorizeAttribute : IAuthorizeData

在asp.net core 3.x中 启动阶段可以配置一个全局策略列表,它一直存在于系统中,暂时称为“静态策略列表”
在每次执行授权检查时也需要一个策略,暂时称为“运行时授权策略”,授权中间件执行时就会创建此策略,然后调用AuthorizationService根据此策略进行权限判断,那此策略中的“授权依据”和“身份验证方案”这俩列表从哪来的呢?就是在Action通过AuthorizeAttribute来定制的,它实现 IAuthorizeData接口
如果你对早期版本mvc有一丢丢了解的话,你可能记得有个授权过滤器的概念AuthorizeAttribute,在Action执行前会先去做授权判断,若成功才会继续执行Action,否则就返回403.
在asp.net core 3.x中不是这样了,AuthorizeAttribute只是用来定制当前授权策略(AuthorizationPolicy)的,并不是过滤器,它实现IAuthorizeData接口,此接口定义如下:

public interface IAuthorizeData
{
    string Policy { get; set; }//直接指定此Action将来授权验证时要使用的授权策略AuthorizationPolicy,此策略会被合并到当前授权策略
    string Roles { get; set; } //它会创建一个基于角色的授权依据RolesAuthorizationRequirement,此依据会被放入当前授权策略
    string AuthenticationSchemes { get; set; }//它用来定义当前授权策略里要使用哪些身份验证方案
}

Policy属性指明从“静态策略列表”拿到指定策略,然后将其“授权依据”和“身份验证方案”这俩列表合并到“运行时授权策略”
看个例子:

 [Authorize(AuthenticationSchemes = "cookie,jwtBearer")]
 [Authorize(Roles = "manager,admin")]
 [Authorize(policy:"test")]
 [Authorize]
 public IActionResult Privacy()
 {
      return View();
 }

以上定制只是针对使用授权中间件来做权限判断时,对当前授权策略进行定制。若我们直接在业务代码中调用AuthorizationService手动进行权限判断呢,就截止调用咯。

services.AddAuthorization()

AddAuthorization注册了授权相关服务

public static IServiceCollection AddAuthorization(this IServiceCollection services)
        {
            services.AddAuthorizationCore();
            services.AddAuthorizationPolicyEvaluator();
            return services;
        }
        public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
        {
            services.AddOptions();

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

策略评估器IPolicyEvaluator

默认实现PolicyEvaluator,授权中间件委托它来实现身份验证和授权处理,它内部会调用AuthorizationService,进而执行所有授权处理器AuthorizationHandler

    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)
        {
            var result = await _authorization.AuthorizeAsync(context.User, resource, policy);
            if (result.Succeeded)
            {
                return PolicyAuthorizationResult.Success();
            }
            return (authenticationResult.Succeeded)
                ? PolicyAuthorizationResult.Forbid(result.Failure)
                : PolicyAuthorizationResult.Challenge();
        }
    }

IAuthorizationService(授权服务)

IAuthorizationService就是用来封装授权检查的,我们在不同的点都可以来调用它执行权限判断。在授权中间件和在业务逻辑代码中手动进行授权检查时都是调用此接口
默认实现DefaultAuthorizationService,它内部会去调用AuthorizationHandler来进行权限判断。

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)
        {
            _options = options.Value;
            _handlers = handlers;
            _policyProvider = policyProvider;
            _logger = logger;
            _evaluator = evaluator;
            _contextFactory = contextFactory;
        }

        //检查用户是否满足指定资源的特定要求。
        public virtual 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;
                }
            }

            var result = _evaluator.Evaluate(authContext);
            if (result.Succeeded)
            {
                _logger.UserAuthorizationSucceeded();
            }
            else
            {
                _logger.UserAuthorizationFailed(result.Failure!);
            }
            return result;
        }

        //检查用户是否满足特定的授权策略。
        public virtual async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName)
        {
            var policy = await _policyProvider.GetPolicyAsync(policyName);
            return await this.AuthorizeAsync(user, resource, policy);
        }
    }

IAuthorizationEvaluator(授权评估器)

默认实现DefaultAuthorizationEvaluator,授权处理器AuthorizationHandler在执行完授权后,结果是存储在AuthorizationHandlerContext中的,这里的评估器只是根据AuthorizationHandlerContext创建一个授权结果AuthorizationResult,给了我们一个机会来加入自己的代码而已

public class DefaultAuthorizationEvaluator : IAuthorizationEvaluator
    {
        //确定授权结果是否成功。
        public AuthorizationResult Evaluate(AuthorizationHandlerContext context)
            => context.HasSucceeded
                ? AuthorizationResult.Success()
                : AuthorizationResult.Failed(context.HasFailed
                    ? AuthorizationFailure.ExplicitFail()
                    : AuthorizationFailure.Failed(context.PendingRequirements));
    }

IAuthorizationPolicyProvider(授权策略提供器)

默认实现DefaultAuthorizationPolicyProvider,可以通过它来获取指定名称的授权,它就是从全局授权策略列表里去找,也就是上面说的AuthorizationOptions中


    public class DefaultAuthorizationPolicyProvider : IAuthorizationPolicyProvider
    {
        private readonly AuthorizationOptions _options;
        private Task<AuthorizationPolicy>? _cachedDefaultPolicy;
        private Task<AuthorizationPolicy?>? _cachedFallbackPolicy;

        public DefaultAuthorizationPolicyProvider(IOptions<AuthorizationOptions> options)
        {
            _options = options.Value;
        }

        public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
        {
            if (_cachedDefaultPolicy == null || _cachedDefaultPolicy.Result != _options.DefaultPolicy)
            {
                _cachedDefaultPolicy = Task.FromResult(_options.DefaultPolicy);
            }

            return _cachedDefaultPolicy;
        }

        public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
        {
            if (_cachedFallbackPolicy == null || _cachedFallbackPolicy.Result != _options.FallbackPolicy)
            {
                _cachedFallbackPolicy = Task.FromResult(_options.FallbackPolicy);
            }

            return _cachedFallbackPolicy;
        }

        public virtual Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
        {
            return Task.FromResult(_options.GetPolicy(policyName));
        }
    }

IAuthorizationHandlerContextFactory(授权处理器上下文对象的工厂)

默认实现DefaultAuthorizationHandlerContextFactory,授权处理器AuthorizationHandler在授权时需要传入AuthorizationHandlerContext(上面说了授权完成后的结果也存储在里面)。所以在执行授权处理器之前需要构建这个上下文对象,就是通过这个工厂构建的,主要的数据来源就是 当前 或者 指定的 授权策略AuthorizationPolicy

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

IAuthorizationHandlerProvider

授权处理器提供器IAuthorizationHandlerProvider:默认实现DefaultAuthorizationHandlerProvider,通过它来获取系统中所有的授权处理器,其实就是从IOC容器中获取

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

AuthorizationMiddleware(授权中间件)

上面我们介绍了何时实施授权检查,授权中间件(AuthorizationMiddleware)就是其中最为常用的一个授权检查点,相当于是一个授权检查的入口方法,它在进入MVC中间件之前就可以做授权判断,所以比之前的在Action上做判断更早。并且由于授权检查是根据终结点的,因此同一套授权代码可以应用在mvc/webapi/razorPages...等多种web框架。由于授权检查依赖当前访问的终结点 和 当前登录用户,因此 授权中间件 应该在 路由中间件 和 身份验证中间件 之后注册

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
       //略....
       app.UseRouting();
       app.UseAuthentication();
       app.UseAuthorization();
       app.UseEndpoints);
}

它的核心步骤大致如下:

  1. 从当前请求拿到终结点
  2. 通过终结点拿到其关联的IAuthorizeData集合
  3. 通过IAuthorizeData集合创建一个复合型授权策略
  4. 遍历策略中的身份验证方案获取多张证件,最后合并放入HttpContext.User中
  5. 若Action上应用了IAllowAnonymous,则放弃授权检查
  6. 调用IAuthorizationService执行授权检查
  7. 若授检查结果为质询,则遍历策略所有的身份验证方案,进行质询,若策略里木有身份验证方案则使用默认身份验证方案进行质询
  8. 若授权评估拒绝就直接调用身份验证方案进行拒绝
  9. 所以重点是可以在执行mvc中间件之前拿到终结点及其之上定义的AuthorizeAttribute,从其中的数据就可以构建出本次权限判断的“授权策略”,有了授权策略就可以通过AuthorizationService执行授权判断,内部会使用到授权处理器AuthorizationHandler
点击查看AuthorizationMiddleware源码
public class AuthorizationMiddleware
    {
        private const string SuppressUseHttpContextAsAuthorizationResource = "Microsoft.AspNetCore.Authorization.SuppressUseHttpContextAsAuthorizationResource";


        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)
        {
            var endpoint = context.GetEndpoint();

            if (endpoint != null)
            {
                //EndpointRoutingMiddleware使用这个标志来检查授权中间件是否处理了端点上的认证元数据。
                context.Items["__AuthorizationMiddlewareWithEndpointInvoked"] = new object();
            }

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

            if (endpoint?.Metadata.GetMetadata<IAllowAnonymous>() != null)
            {
                await _next(context);
                return;
            }

            object? resource;
            if (AppContext.TryGetSwitch(SuppressUseHttpContextAsAuthorizationResource, out var useEndpointAsResource) && useEndpointAsResource)
            {
                resource = endpoint;
            }
            else
            {
                resource = context;
            }
            
            var authorizeResult = await policyEvaluator.AuthorizeAsync(policy, authenticateResult, context, resource);
            var authorizationMiddlewareResultHandler = context.RequestServices.GetRequiredService<IAuthorizationMiddlewareResultHandler>();
            await authorizationMiddlewareResultHandler.HandleAsync(_next, context, policy, authorizeResult);
        }
    }

参考:
https://www.cnblogs.com/RainingNight/tag/Authorization/
https://www.cnblogs.com/jionsoft/tag/授权/
https://github.com/dotnet/aspnetcore/tree/b56f84131af2e1ece61241a016e191f5f2fe3fc0/src/Security

posted @ 2020-12-14 20:52  .Neterr  阅读(1089)  评论(0编辑  收藏  举报