文章目录

简介

开篇提到过,认证主要解决的是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;
}

授权时序图

授权项目还是比较好理解的,微软提供了一个基于策略的授权模型,大部门的具体的业务代码还是需要自己去实现的。

classDiagram
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_授权框架的更多相关文章

  1. AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象

    系列文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3. ...

  2. AspNetCore3.1_Secutiry源码解析_3_Authentication_Cookies

    系列文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心流程 AspNetCore3. ...

  3. AspNetCore3.1_Secutiry源码解析_4_Authentication_JwtBear

    title: "AspNetCore3.1_Secutiry源码解析_4_Authentication_JwtBear" date: 2020-03-22T16:29:29+08: ...

  4. AspNetCore3.1_Secutiry源码解析_5_Authentication_OAuth

    title: "AspNetCore3.1_Secutiry源码解析_5_Authentication_OAuth" date: 2020-03-24T23:27:45+08:00 ...

  5. AspNetCore3.1_Secutiry源码解析_6_Authentication_OpenIdConnect

    title: "AspNetCore3.1_Secutiry源码解析_6_Authentication_OpenIdConnect" date: 2020-03-25T21:33: ...

  6. AspNetCore3.1_Secutiry源码解析_1_目录

    文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3.1_ ...

  7. [源码解析] 并行分布式框架 Celery 之架构 (2)

    [源码解析] 并行分布式框架 Celery 之架构 (2) 目录 [源码解析] 并行分布式框架 Celery 之架构 (2) 0x00 摘要 0x01 上文回顾 0x02 worker的思考 2.1 ...

  8. [源码解析] 并行分布式框架 Celery 之架构 (1)

    [源码解析] 并行分布式框架 Celery 之架构 (1) 目录 [源码解析] 并行分布式框架 Celery 之架构 (1) 0x00 摘要 0x01 Celery 简介 1.1 什么是 Celery ...

  9. [源码解析] 并行分布式框架 Celery 之 worker 启动 (1)

    [源码解析] 并行分布式框架 Celery 之 worker 启动 (1) 目录 [源码解析] 并行分布式框架 Celery 之 worker 启动 (1) 0x00 摘要 0x01 Celery的架 ...

随机推荐

  1. win7 任务栏 无法固定文件夹和文件 解决方法

    2010年开始使用win7,一直都在用一个功能,把常用的文件夹和文件都直接锁定到任务栏,方便使用. 最近这个功能一直有问题,开始只是重启以后,以前固定的文件没有了,这个也懒的去弄它,没了在添加一次. ...

  2. kafka&&kafka-manager部署安装

    一.zk集群部署 二.kafka部署安装 1.创建kafka用户和日志路径,(直接执行) groupadd kafka useradd -g kafka kafka mkdir -p /web/kaf ...

  3. TCP可靠传输的工作原理

    TCP可靠传输的工作原理 一.停止等待协议 1.1.简介 在发送完一个分组后,必须暂时保留已发送的分组的副本. 分组和确认分组都必须进行编号. 超时计时器的重传时间应当比数据在分组传输的平均往返时间更 ...

  4. Java入门教程九(封装继承多态)

    封装 封装就是将对象的属性和方法相结合,通过方法将对象的属性和实现细节保护起来,实现对象的属性隐藏.做法就是:修改属性的可见性来限制对属性的访问,并为每个属性创建一对取值(getter)方法和赋值(s ...

  5. 分布式系统一致性问题与Raft算法(下)

    上一篇讲述了什么是分布式一致性问题,以及它难在哪里,liveness和satefy问题,和FLP impossibility定理.有兴趣的童鞋可以看看分布式系统一致性问题与Raft算法(上). 这一节 ...

  6. 7-4 jmu-python-判断是否构成三角形 (10 分)

    7-4 jmu-python-判断是否构成三角形 (10 分)   输入三角形的三边,判断是否能构成三角形.若能构成输出yes,否则输出no. 输入格式: 在一行中直接输入3个整数,3个整数之间各用一 ...

  7. 【开发技巧】再见,BLE的那些坑!

    蓝牙,平常你用的多吗?上班路上戴着蓝牙耳机听音乐.看视频打开蓝牙分享个人热点给小伙伴们解锁共享单车时,打开蓝牙就能迅速解锁...... BLE-蓝牙低能耗技术,方便了我们的生活,但是开发者在开发过程中 ...

  8. TCP/IP基础总结性学习(6)

    HTTP 首部 一. HTTP 报文首部 1.HTTP 报文的结构: 2.HTTP 请求报文 图示: 举例子: 3.HTTP 响应报文: 下面的示例是访问 http://hackr.jp 时,请求报文 ...

  9. c语言之学生管理系统

    0x00 学生管理系统 说到学生管理系统,对于每一个初学c语言的人都是一道不得不过的砍.不过,学习c,我觉得每个人都应该写一个学生管理系统,至于为什么,我想倘若连一个学生管理系统都写不好,哪么他的c是 ...

  10. 【DPDK】谈谈DPDK如何实现bypass内核的原理 其一 PCI设备与UIO驱动

    [前言] 随着网络的高速发展,对网络的性能要求也越来越高,DPDK框架是目前的一种加速网络IO的解决方案之一,也是最为流行的一套方案.DPDK通过bypass内核协议栈与内核驱动,将驱动的工作从内核态 ...