ASP.NET Core框架探索之Authorization
今天我们一起来探索一下ASP.NET Core框架中的Authorization。我们知道请求进入管道处理流程先会使用Authentication进行用户认证,然后使用Authorization进行用户授权。如果没有看过认证过程的大家可以先转到Authentication这一篇。
AddAuthorization
首先还是一样的方式,在管道中需要使用Authorization服务,我们首先需要向容器中添加相关服务,然后在管道处理中使用UseAuthorization,有人可能会比较疑惑,为什么框架自动生成好的项目中只有UseAuthorization而没有看到AddAuthorization这样的代码呢?
1 private static IMvcCoreBuilder AddControllersCore(IServiceCollection services)
2 {
3 return services.AddMvcCore().AddAuthorization();
4 }
5
6 public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
7 {
8 if (services == null)
9 {
10 throw new ArgumentNullException(nameof(services));
11 }
12
13 services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());
14 services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());
15 services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());
16 services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>());
17 services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());
18 services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());
19 return services;
20 }
通过源码我们可以看到这个过程其实是在添加MVC服务的时候做的,而AddAuthorization就是把用户授权过程中必要的一些服务注入到容器。
下面我们来看在管道中添加的处理程序UseAuthorization,去了解一下框架是如何进行用户授权的。
UseAuthorization
在管道处理中,授权过程的逻辑是定义在AuthorizationMiddleware中间件里面的。当用户请求某个资源时处理过程来到管道时,中间件中必须要先看一下这个资源是否需要授权,如果使用了AuthorizeAttribute进行标记,那么表明就是需要授权的,所以最先的步骤是需要拿到用户添加在
AuthorizeAttribute中的信息,而MVC流程中控制器和action相关的信息会被保存在获取的Endpoint的元数据上,所以第一步就是从元数据中获取到IAuthorizeData的信息。
下面我们先学习一下授权中的几个重点对象的概念:
IAuthorizeData
我们知道,如果需要对某个请求的资源开启授权校验,就要在某个控制器或者action上添加Authorize的特性,比如需要角色名称是管理员,我们一般会加上[Authorize(Roles ="admin")]。
我们来看一下AuthorizeAttribute这个特性的源码:
1 public class AuthorizeAttribute : Attribute, IAuthorizeData
2 {
3 ...
4
5 public string Policy { get; set; }
6
7 public string Roles { get; set; }
8
9 public string AuthenticationSchemes { get; set; }
10 }
可以看到,特性继承于IAuthorizeData接口,该特性中定义有三个属性:
Policy:用于定义授权基于的策略名称
Roles:用于定义授权基于的角色名称
AuthenticationSchemes:用于定义采用该授权方式前使用的用户认证方案
这三个属性正是IAuthorizeData接口中定义的属性,在管道中经过UseRouting中间件的处理匹配到合适的终结点时,请求资源上添加的IAuthorizeData信息将会被添加到终结点的元数据中。
IAuthorizationRequirement和AuthorizationHandler<TRequirement>
IAuthorizationRequirement接口是一个空接口,无实际用处,只是用来进行标记其实现类是一个授权规则。
AuthorizationHandler<TRequirement>是一个用于定义授权处理规则的抽象基类,他继承于IAuthorizationHandler接口,该接口只有一个HandleAsync的接口,系统请求的资源需要什么授权规则即是通过继承此抽象基类重写HandleAsync进行定义的,开发人员可以根据具体的场景定义授权规则。
我们拿系统自带的角色授权规则RolesAuthorizationRequirement来看,我们希望基于roleName进行授权,于是继承AuthorizationHandler<RolesAuthorizationRequirement>和IAuthorizationRequirement,在授权处理中,通过判断User用户信息中是否包含指定的角色名称来返回授权是否成功,下面是RolesAuthorizationRequirement的源码:
1 public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
2 {
3 public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles)
4 {
5 if (allowedRoles == null)
6 {
7 throw new ArgumentNullException(nameof(allowedRoles));
8 }
9
10 if (allowedRoles.Count() == 0)
11 {
12 throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty);
13 }
14 AllowedRoles = allowedRoles;
15 }
16
17 public IEnumerable<string> AllowedRoles { get; }
18
19 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
20 {
21 if (context.User != null)
22 {
23 bool found = false;
24 if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
25 {
26 // Review: What do we want to do here? No roles requested is auto success?
27 }
28 else
29 {
30 found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
31 }
32 if (found)
33 {
34 context.Succeed(requirement);
35 }
36 }
37 return Task.CompletedTask;
38 }
39
40 }
在实际业务场景中,涉及到的授权规则可能是多种多样的,有可能希望用户性别是女生,也有可能需要用户年龄满18岁,还有可能是多种条件限制的复杂情况等等,所以我们就可以仿照角色授权规则去自定义Requirement类继承此抽象类和接口。
AuthorizationPolicy
由于在授权的过程中,某个资源的授权规则可能不止一个,而是需要满足多个授权规则,于是我们需要有一个能够表明某次请求需要的授权规则的集合,这就是AuthorizationPolicy的作用了,我们先看源码:

1 public class AuthorizationPolicy
2 {
3 /// <summary>
4 /// Creates a new instance of <see cref="AuthorizationPolicy"/>.
5 /// </summary>
6 /// <param name="requirements">
7 /// The list of <see cref="IAuthorizationRequirement"/>s which must succeed for
8 /// this policy to be successful.
9 /// </param>
10 /// <param name="authenticationSchemes">
11 /// The authentication schemes the <paramref name="requirements"/> are evaluated against.
12 /// </param>
13 public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes)
14 {
15 if (requirements == null)
16 {
17 throw new ArgumentNullException(nameof(requirements));
18 }
19
20 if (authenticationSchemes == null)
21 {
22 throw new ArgumentNullException(nameof(authenticationSchemes));
23 }
24
25 if (requirements.Count() == 0)
26 {
27 throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
28 }
29 Requirements = new List<IAuthorizationRequirement>(requirements).AsReadOnly();
30 AuthenticationSchemes = new List<string>(authenticationSchemes).AsReadOnly();
31 }
32
33
34 public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
35
36 public IReadOnlyList<string> AuthenticationSchemes { get; }
37
38 public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
39 {
40 ...
41
42 AuthorizationPolicyBuilder policyBuilder = null;
43
44 foreach (var authorizeDatum in authorizeData)
45 {
46 if (policyBuilder == null)
47 {
48 policyBuilder = new AuthorizationPolicyBuilder();
49 }
50
51 var useDefaultPolicy = true;
52 if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
53 {
54 var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
55
56 useDefaultPolicy = false;
57 }
58
59 var rolesSplit = authorizeDatum.Roles?.Split(',');
60 if (rolesSplit != null && rolesSplit.Any())
61 {
62 var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
63 policyBuilder.RequireRole(trimmedRolesSplit);
64 useDefaultPolicy = false;
65 }
66
67 var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
68 if (authTypesSplit != null && authTypesSplit.Any())
69 {
70 foreach (var authType in authTypesSplit)
71 {
72 if (!string.IsNullOrWhiteSpace(authType))
73 {
74 policyBuilder.AuthenticationSchemes.Add(authType.Trim());
75 }
76 }
77 }
78
79 if (useDefaultPolicy)
80 {
81 policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
82 }
83 }
84 ...
85
86 return policyBuilder?.Build();
87 }
88 }
在类中定义有两个集合属性:Requirements和AuthenticationSchemes,分别用来存放授权规则IAuthorizationRequirement和认证方案名称。
在CombineAsync方法中,该方法会接受传入进来的IEnumerable<IAuthorizeData>集合,经过循环,把每个 IAuthorizeData中的三个属性进行了转换:
通过Policy名称在IAuthorizationPolicyProvider中查询返回AuthorizationPolicy,该对象中的集合属性将会被加入到AuthorizationPolicyBuilder中;
通过Roles在AuthorizationPolicyBuilder中使用RolesAuthorizationRequirement构造函数生成RolesAuthorizationRequirement对象并加入到Requirements集合,而AuthenticationSchemes则是加入到了AuthorizationPolicyBuilder对象中的AuthenticationSchemes集合
最后通过使用AuthorizationPolicyBuilder对象的Build方法将对象中的Requirements和AuthenticationSchemes作为AuthorizationPolicy构造函数的参数生成AuthorizationPolicy对象。由此可见CombineAsync即是完成将IEnumerable<IAuthorizeData>中的属性进行转换生成此次请求资源的完整授权策略。
而通过AuthorizationPolicy,也是顺利的将IAuthorizeData接口与IAuthorizationRequirement和AuthorizationHandler<TRequirement>联系起来了,也可以说是把用户添加在AuthorizeAttribute中的授权要求转换成了具体的AuthorizationRequirement对象,执行对象中的处理逻辑即可完成授权。
其实介绍完上述三个概念之后,我们的授权大致逻辑基本就清晰了:
1.通过请求资源匹配的Endpoint终结点获取到元数据中的IAuthorizeData;
2.将资源设置的IAuthorizeData中的三个属性经过查询和转换包装为一个整体的授权策略AuthorizationPolicy;
3.使用AuthorizationPolicy中的AuthenticationSchemes完成用户信息的认证;
4.使用AuthorizationPolicy中的Requirements完成用户授权
疑问
以上介绍了授权中非常重要的几个概念以及授权大致的基本逻辑,但是仔细思考,上述流程中还存在一些疑惑:
1.我们知道使用[Authorize(Roles ="admin")]基于角色授权,最终会根据名称生成RolesAuthorizationRequirement对象,那我们如何基于Policy进行授权呢?
如果需要使用Policy进行授权,我们一般需要在添加授权服务到容器的时候定义好PolicyName以及策略内容,例如下面的代码所示:
1 services.AddAuthorization(option =>
2 {
3 option.AddPolicy("CustomPolicy", authorizationPolicyBuilder =>
4 {
5 authorizationPolicyBuilder
6 .RequireRole("admin")
7 .RequireClaim(ClaimTypes.Email);
8 });
9 });
然后在需要控制的资源上加上[Authorize(Policy = "CustomPolicy")] 即可。
2.通过AddAuthorization的委托参数设置AuthorizationOptions后,我们是如何在需要的时候获取到的呢?
AuthorizationOptions包含一个用于存储PolicyName和Policy内容关系的字典集合IDictionary<string, AuthorizationPolicy>,AddPolicy负责往字典中添加对应关系后,通过Configure选项模式保存AuthorizationOptions的参数配置到容器中,等到需要获取的时候通过IOptions即可获取到对象信息,使用PollicyName即可从字典集合中获取对应的AuthorizationPolicy。这也解释了在AuthorizationPolicy.CombineAsync方法中,我们通过PolicyName在IAuthorizationPolicyProvider中获取AuthorizationPolicy时,IAuthorizationPolicyProvider中的这个对象又是怎么来的,其实IAuthorizationPolicyProvider的实现中就是通过选项模式获取到AuthorizationOptions对象的。
3.系统在进入用户授权环节之前已经经过了使用默认的认证方案进行用户认证,为什么在AuthorizeAttribute这个授权特性中还会存在AuthenticationSchemes呢?也就是说最终转换到AuthorizationPolicy中的AuthenticationSchemes集合有什么用处呢?
在往容器中添加用户认证服务的时候,我们一般需要指定默认的认证方案,而我们的认证服务往往是可以添加多个认证方案的,在某些场景下,需要限定请求的资源在用户授权时使用非默认方案进行认证时,这个时候特性中的AuthenticationSchemes就起到了作用,开发人员能够通过灵活的指定AuthenticationSchemes属性,限制资源的证方案然后进行授权。
写在最后
这次关于Authorization的分享就到这里,考虑了很久应该用怎样的方式来进行本次分享,最终还是经过梳理要点后的这种方式可能会比较清晰,希望对大家有所收获,还有问题的朋友可以评论区留言讨论哈。新人博主,喜欢的话请大家点个赞,也非常欢迎大家提出宝贵的意见!!!
ASP.NET Core框架探索之Authorization的更多相关文章
- ASP.NET Core框架探索之Authentication
今天我们来探索一下ASP.NET Core中关于权限认证,所谓权限认证,就是通过某些方式获取到用户的信息. 需要开启权限认证,我们首先需要在容器中注入认证服务,使用services.AddAuthen ...
- ASP.NET Core框架探索(一)
今天我们来结合源码来探究一下ASP.NET CORE Web框架的运行原理. 可以先整体看一下下面这张基于源码分析过程的一个总结大纲,包含各环节完成的关键步骤: 下面我们将一起来结合源码探索启动一个A ...
- 一个Mini的ASP.NET Core框架的实现
一.ASP.NET Core Mini 在2019年1月的微软技术(苏州)俱乐部成立大会上,蒋金楠老师(大内老A)分享了一个名为“ASP.NET Core框架揭秘”的课程,他用不到200行的代码实现了 ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘>的分享.在此次分享中,我按照ASP.NET Core自身的运行原理和设计 ...
- ASP.NET Core 框架源码地址
ASP.NET Core 框架源码地址 https://github.com/dotnet/corefx 这个是.net core的 开源项目地址 https://github.com/aspnet ...
- 了解ASP.NET Core框架的本质
了解ASP.NET Core框架的本质 ASP.NET Core自身的运行原理和设计思想创建了一个 “迷你版” 的ASP.NET Core框架,并且利用这个 “极简” 的模拟框架阐述了ASP.NET ...
- 一步步完成“迷你版” 的ASP.NET Core框架
一 前言 Artech 分享了 200行代码,7个对象--让你了解ASP.NET Core框架的本质 . 用一个极简的模拟框架阐述了ASP.NET Core框架最为核心的部分. 这里一步步来完成这个迷 ...
- 200行代码,7个对象——让你了解ASP.NET Core框架的本质
原文:200行代码,7个对象--让你了解ASP.NET Core框架的本质 2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为<ASP.NET Core框架揭秘&g ...
- ASP.NET Core框架的本质
源文章地址:http://www.cnblogs.com/artech/p/inside-asp-net-core-framework.html 1.从Hello World谈起 当我们最开始学习一门 ...
随机推荐
- 【转载】Nginx简介及使用Nginx实现负载均衡的原理
原文地址:http://blog.csdn.net/u014749862/article/details/50522276 是什么? Nginx 这个轻量级.高性能的 web server 主要可以干 ...
- linux增加用户组,并在用户组下添加指定用户
groupadd mysql #1 useradd -g mysql[用户组] mysql[用户名] #2 useradd mysql[用户名] -g mysql[用户组]
- Ext原码学习之Ext-more.js
// JavaScript Document Ext.apply(Ext,{ userAgent:navigator.userAgent.toLowerCase(), cache:{}, isSeed ...
- Spark算子 - reduce
释义 reduce将partition内所有记录最终计算成1个记录,结果类型与reduce 时数据类型一致 方法签名如下: def reduce(f: (T, T) => T): T = wit ...
- MXNet源码分析 | Gluon接口分布式训练流程
本文主要基于MXNet1.6.0版本,对Gluon接口的分布式训练过程进行简要分析. 众所周知,KVStore负责MXNet分布式训练过程中参数的同步,那么它究竟是如何应用在训练中的呢?下面我们将从G ...
- GAN实战笔记——第三章第一个GAN模型:生成手写数字
第一个GAN模型-生成手写数字 一.GAN的基础:对抗训练 形式上,生成器和判别器由可微函数表示如神经网络,他们都有自己的代价函数.这两个网络是利用判别器的损失记性反向传播训练.判别器努力使真实样本输 ...
- 【摸鱼神器】UCode Cms管理系统 内置超好用的代码生成器 解决多表连接痛点
一.序言 UCode Cms管理系统是面向企业级应用软件开发的脚手架.当前版本1.3.4.快速体验: git clone https://gitee.com/decsa/demo-cms.git (一 ...
- ios plist获取权限
最近太忙了,没有时间写vue 这个权限获取有点坑,极不好记,所以备份一份 <key>NSVideoSubscriberAccountUsageDescription</key> ...
- curl常用参数详解及示例
curl简介 curl是一个开源的命令行工具,它基于网络协议,对指定URL进行网络传输,得到数据后不任何具体处理(如:html的渲染等),直接显示在"标准输出"(stdout)上. ...
- Tableau退出已成定局,关键是用户如何“软着陆”
近期,BI界发生了一件大事,引起了大家的热议. 简单来说:Tableau停止在中国的原厂服务,把售后.解决方案等归到新加坡,在中国区域的运营将有阿里接管. 大部分业内人士认为中国区业务可能以出售.代理 ...