AspNetCore3.1_Secutiry源码解析_8_Authorization_授权框架
文章目录
- AspNetCore3.1_Secutiry源码解析_1_目录
 - AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目
 - AspNetCore3.1_Secutiry源码解析_3_Authentication_Cookies
 - AspNetCore3.1_Secutiry源码解析_4_Authentication_JwtBear
 - AspNetCore3.1_Secutiry源码解析_5_Authentication_OAuth
 - AspNetCore3.1_Secutiry源码解析_6_Authentication_OpenIdConnect
 - AspNetCore3.1_Secutiry源码解析_7_Authentication_其他
 - AspNetCore3.1_Secutiry源码解析_8_Authorization_授权框架
 
简介
开篇提到过,认证主要解决的是who are you,授权解决的是 are you allowed的问题。各种认证架构可以帮我们知道用户身份(claims),oauth等架构的scope字段能够控制api服务级别的访问权限,但是更加细化和多变的功能授权不是它们的处理范围。
微软的Authorization项目提供了基于策略的灵活的授权框架。
推荐看下面博客了解,我主要学习和梳理源码。
https://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html
依赖注入
注入了以下接口,提供了默认实现
- IAuthorizationService :授权服务,主干服务
 - IAuthorizationPolicyProvider : 策略提供类
 - IAuthorizationHandlerProvider:处理器提供类
 - IAuthorizationEvaluator:校验类
 - IAuthorizationHandlerContextFactory:授权上下文工厂
 - IAuthorizationHandler:授权处理器,这个是注入的集合,一个策略可以有多个授权处理器,依次执行
 - 配置类:AuthorizationOptions
 
微软的命名风格还是比较一致的
Service:服务
Provider:某类的提供者
Evaluator:校验预处理类
Factory:工厂
Handler:处理器
Context:上下文
看源码的过程,不仅可以学习框架背后原理,还可以学习编码风格和设计模式,还是挺有用处的。
/// <summary>
/// Adds authorization services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(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;
}
/// <summary>
/// Adds authorization services to the specified <see cref="IServiceCollection" />.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param>
/// <param name="configure">An action delegate to configure the provided <see cref="AuthorizationOptions"/>.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action<AuthorizationOptions> configure)
{
    if (services == null)
    {
        throw new ArgumentNullException(nameof(services));
    }
    if (configure != null)
    {
        services.Configure(configure);
    }
    return services.AddAuthorizationCore();
}
配置类 - AuthorizationOptions
- PolicyMap:策略名称&策略的字典数据
 - InvokeHandlersAfterFailure: 授权处理器失败后是否触发下一个处理器,默认true
 - DefaultPolicy:默认策略,构造了一个RequireAuthenticatedUser策略,即需要认证用户,不允许匿名访问。现在有点线索了,为什么api一加上[Authorize],就会校验授权。
 - FallbackPolicy:保底策略。没有任何策略的时候会使用保底策略。感觉有点多此一举,不是给了个默认策略吗?
 - AddPolicy:添加策略
 - GetPolicy:获取策略
 
/// <summary>
/// Provides programmatic configuration used by <see cref="IAuthorizationService"/> and <see cref="IAuthorizationPolicyProvider"/>.
/// </summary>
public class AuthorizationOptions
{
    private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
    /// <summary>
    /// Determines whether authentication handlers should be invoked after a failure.
    /// Defaults to true.
    /// </summary>
    public bool InvokeHandlersAfterFailure { get; set; } = true;
    /// <summary>
    /// Gets or sets the default authorization policy. Defaults to require authenticated users.
    /// </summary>
    /// <remarks>
    /// The default policy used when evaluating <see cref="IAuthorizeData"/> with no policy name specified.
    /// </remarks>
    public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
    /// <summary>
    /// Gets or sets the fallback authorization policy used by <see cref="AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider, IEnumerable{IAuthorizeData})"/>
    /// when no IAuthorizeData have been provided. As a result, the AuthorizationMiddleware uses the fallback policy
    /// if there are no <see cref="IAuthorizeData"/> instances for a resource. If a resource has any <see cref="IAuthorizeData"/>
    /// then they are evaluated instead of the fallback policy. By default the fallback policy is null, and usually will have no
    /// effect unless you have the AuthorizationMiddleware in your pipeline. It is not used in any way by the
    /// default <see cref="IAuthorizationService"/>.
    /// </summary>
    public AuthorizationPolicy FallbackPolicy { get; set; }
    /// <summary>
    /// Add an authorization policy with the provided name.
    /// </summary>
    /// <param name="name">The name of the policy.</param>
    /// <param name="policy">The authorization policy.</param>
    public void AddPolicy(string name, AuthorizationPolicy policy)
    {
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
        if (policy == null)
        {
            throw new ArgumentNullException(nameof(policy));
        }
        PolicyMap[name] = policy;
    }
    /// <summary>
    /// Add a policy that is built from a delegate with the provided name.
    /// </summary>
    /// <param name="name">The name of the policy.</param>
    /// <param name="configurePolicy">The delegate that will be used to build the policy.</param>
    public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
    {
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
        if (configurePolicy == null)
        {
            throw new ArgumentNullException(nameof(configurePolicy));
        }
        var policyBuilder = new AuthorizationPolicyBuilder();
        configurePolicy(policyBuilder);
        PolicyMap[name] = policyBuilder.Build();
    }
    /// <summary>
    /// Returns the policy for the specified name, or null if a policy with the name does not exist.
    /// </summary>
    /// <param name="name">The name of the policy to return.</param>
    /// <returns>The policy for the specified name, or null if a policy with the name does not exist.</returns>
    public AuthorizationPolicy GetPolicy(string name)
    {
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
        return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
    }
}
IAuthorizationService - 授权服务 - 主干逻辑
接口定义了授权方法,有两个重载,一个是基于requirements校验,一个是基于policyName校验。
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
看下默认实现DefaultAuthorizationService的处理,逻辑还是比较简单
- 获取策略
 - 获取策略的授权条件
 - 获取授权上下文
 - 获取处理器集合
 - 处理器依次执行,结果存入上下文
 - 校验器验证上下文
 - 返回授权结果类
 
 /// <summary>
/// The default implementation of an <see cref="IAuthorizationService"/>.
/// </summary>
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;
    /// <summary>
    /// Creates a new instance of <see cref="DefaultAuthorizationService"/>.
    /// </summary>
    /// <param name="policyProvider">The <see cref="IAuthorizationPolicyProvider"/> used to provide policies.</param>
    /// <param name="handlers">The handlers used to fulfill <see cref="IAuthorizationRequirement"/>s.</param>
    /// <param name="logger">The logger used to log messages, warnings and errors.</param>
    /// <param name="contextFactory">The <see cref="IAuthorizationHandlerContextFactory"/> used to create the context to handle the authorization.</param>
    /// <param name="evaluator">The <see cref="IAuthorizationEvaluator"/> used to determine if authorization was successful.</param>
    /// <param name="options">The <see cref="AuthorizationOptions"/> used.</param>
    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;
    }
    /// <summary>
    /// Checks if a user meets a specific set of requirements for the specified resource.
    /// </summary>
    /// <param name="user">The user to evaluate the requirements against.</param>
    /// <param name="resource">The resource to evaluate the requirements against.</param>
    /// <param name="requirements">The requirements to evaluate.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy otherwise <value>false</value>.
    /// </returns>
    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;
    }
    /// <summary>
    /// Checks if a user meets a specific authorization policy.
    /// </summary>
    /// <param name="user">The user to check the policy against.</param>
    /// <param name="resource">The resource the policy should be checked with.</param>
    /// <param name="policyName">The name of the policy to check against a specific context.</param>
    /// <returns>
    /// A flag indicating whether authorization has succeeded.
    /// This value is <value>true</value> when the user fulfills the policy otherwise <value>false</value>.
    /// </returns>
    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);
    }
}
默认策略 - 需要认证用户
默认策略添加了校验条件DenyAnonymousAuthorizationRequirement
public AuthorizationPolicyBuilder RequireAuthenticatedUser()
{
    Requirements.Add(new DenyAnonymousAuthorizationRequirement());
    return this;
}
校验上下文中是否存在认证用户信息,验证通过则在上下文中将校验条件标记为成功。
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement)
    {
        var user = context.User;
        var userIsAnonymous =
            user?.Identity == null ||
            !user.Identities.Any(i => i.IsAuthenticated);
        if (!userIsAnonymous)
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
授权时序图
授权项目还是比较好理解的,微软提供了一个基于策略的授权模型,大部门的具体的业务代码还是需要自己去实现的。
class AuthorizationPolicy{
Requirements
}
class Requirement{
}
class AuthorizationHandler{
}
class IAuthorizationHandler{
+HandleAsync(AuthorizationHandlerContext context)
}
class IAuthorizationRequirement{
}
Requirement-->AuthorizationHandler
AuthorizationHandler-->IAuthorizationHandler
Requirement-->IAuthorizationHandler
Requirement-->IAuthorizationRequirement
中间件去哪了?
开发不需要编写UseAuthorization类似代码,项目中也没发现中间件,甚至找不到 使用AuthorizeAttribute的地方。那么问题来了,框架怎么知道某个方法标记了[Authorize]特性,然后执行校验的呢?
答案是Mvc框架处理的,它读取了节点的[Authorize]和[AllowAnonymous]特性,并触发相应的逻辑。关于Mvc的就不细说了,感兴趣可以翻看源码。
AspNetCore\src\Mvc\Mvc.Core\src\ApplicationModels\AuthorizationApplicationModelProvider.cs。
public void OnProvidersExecuting(ApplicationModelProviderContext context)
{
    if (context == null)
    {
        throw new ArgumentNullException(nameof(context));
    }
    if (_mvcOptions.EnableEndpointRouting)
    {
        // When using endpoint routing, the AuthorizationMiddleware does the work that Auth filters would otherwise perform.
        // Consequently we do not need to convert authorization attributes to filters.
        return;
    }
    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());
            }
        }
    }
}
												
											AspNetCore3.1_Secutiry源码解析_8_Authorization_授权框架的更多相关文章
- AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象
		
系列文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3. ...
 - AspNetCore3.1_Secutiry源码解析_3_Authentication_Cookies
		
系列文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心流程 AspNetCore3. ...
 - AspNetCore3.1_Secutiry源码解析_4_Authentication_JwtBear
		
title: "AspNetCore3.1_Secutiry源码解析_4_Authentication_JwtBear" date: 2020-03-22T16:29:29+08: ...
 - AspNetCore3.1_Secutiry源码解析_5_Authentication_OAuth
		
title: "AspNetCore3.1_Secutiry源码解析_5_Authentication_OAuth" date: 2020-03-24T23:27:45+08:00 ...
 - AspNetCore3.1_Secutiry源码解析_6_Authentication_OpenIdConnect
		
title: "AspNetCore3.1_Secutiry源码解析_6_Authentication_OpenIdConnect" date: 2020-03-25T21:33: ...
 - AspNetCore3.1_Secutiry源码解析_1_目录
		
文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3.1_ ...
 - [源码解析] 并行分布式框架 Celery 之架构 (2)
		
[源码解析] 并行分布式框架 Celery 之架构 (2) 目录 [源码解析] 并行分布式框架 Celery 之架构 (2) 0x00 摘要 0x01 上文回顾 0x02 worker的思考 2.1 ...
 - [源码解析] 并行分布式框架 Celery 之架构 (1)
		
[源码解析] 并行分布式框架 Celery 之架构 (1) 目录 [源码解析] 并行分布式框架 Celery 之架构 (1) 0x00 摘要 0x01 Celery 简介 1.1 什么是 Celery ...
 - [源码解析] 并行分布式框架 Celery 之 worker 启动 (1)
		
[源码解析] 并行分布式框架 Celery 之 worker 启动 (1) 目录 [源码解析] 并行分布式框架 Celery 之 worker 启动 (1) 0x00 摘要 0x01 Celery的架 ...
 
随机推荐
- docker实践-安装wordpress
			
很多人都有搭建wordpress的经历,可能被某些环境的配置搞得焦头乱耳的,这里使用docker,可以很轻松的进行wordpress的搭建工作. 安装 Docker sudo apt-get inst ...
 - vue子组件使用自定义事件向父组件传递数据
			
使用v-on绑定自定义事件可以让子组件向父组件传递数据,用到了this.$emit(‘自定义的事件名称’,传递给父组件的数据) <!DOCTYPE html> <html lang= ...
 - Python---12函数式编程------12.1高阶函数
			
函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...
 - postgresql学习记录1
			
数据库9.3.5,系统fedora20,不同系统操作略有不同. 使用yum 命令安装即可:sudo yum install postgresql,postgresql-server 安装完毕后系统中会 ...
 - Vue1.0用法详解
			
Vue.js 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能实现的 ECMAScript 5 特性. 开发环境部署 可参考使用 vue+webpack. 基本用法 1 2 3 ...
 - MDEV入门
			
------------- MDEV入门------------- 对于我们这些人知道如何使用的mdev ,底漆可能似乎跛.为其他人的mdev是一个神秘的黑匣子,他们听到的是真棒,但不能似乎得到他们的 ...
 - Python——详解collections工具库
			
本文始发于个人公众号:TechFlow,原创不易,求个关注 今天为大家介绍Python当中一个很好用也是很基础的工具库,叫做collections. collection在英文当中有容器的意思,所以顾 ...
 - Swagger默认访问地址
			
Springboot工程格式 http://localhost:8080/swagger-ui.html 非Springboot工程格式(需加个项目名xxx) http://localhost:808 ...
 - 从头解决PKIX path building failed
			
从头解决PKIX path building failed的问题 本篇涉及到PKIX path building failed的原因和解决办法(包括暂时解决和长效解决的方法),也包括HTTP和HTTP ...
 - MyBatis-Plus不写任何resultMap和SQL执行一对一、一对多、多对多关联查询
			
对于一对一,一对多的关联查询,Mybatis-Plus官方示例(mybatis-plus-sample-resultmap)在处理时,需要编写查询方法及配置resultMap,并且写SQL. 为了简化 ...