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谈起 当我们最开始学习一门 ...
随机推荐
- 7月3日下午 微擎芸众商城 设计思路 - laravel路由底层源码解读
学习参考文章 https://learnku.com/articles/13622/the-principle-of-laravel-routing-execution <?phpnamespa ...
- JAVA! static的作用
是静态修饰符,什么叫静态修饰符呢?大家都知道,在程序中任何变量或者代码都是在编译时由系统自动分配内存来存储的,而所谓静态就是指在编译后所分配的内存会一直存在,直到程序退出内存才会释放这个空间,也就是只 ...
- Linux远程访问及控制
Linux远程访问及控制 目录 Linux远程访问及控制 一.SSH远程管理 1. SSH远程管理概述 2. OpenSSH概述 3. 配置OpenSSH服务端 4. sshd服务的验证方式 5. 使 ...
- 基于GDAL库,读取.grd文件(以海洋地形数据为例)Java版
技术背景 海洋地形数据主要是通过美国全球地形起伏数据(GMT)获得,数据格式为grd(GSBG)二进制数据,打开软件通过是Surfer软件,surfer软件可进行数据的编辑处理,以及进一步的可视化表达 ...
- 9、Linux基础--编译安装、压缩打包、定时任务
笔记 1.晨考 1.搭建yum私有仓库的步骤 1.安装工具 yum install createrepo yum-utils nginx -y 2.创建目录 mkdir /opt/test 3.创建包 ...
- Solution -「CF 1586F」Defender of Childhood Dreams
\(\mathcal{Description}\) Link. 定义有向图 \(G=(V,E)\),\(|V|=n\),\(\lang u,v\rang \in E \Leftrightarr ...
- 微服务从代码到k8s部署应有尽有系列(四、用户中心)
我们用一个系列来讲解从需求到上线.从代码到k8s部署.从日志到监控等各个方面的微服务完整实践. 整个项目使用了go-zero开发的微服务,基本包含了go-zero以及相关go-zero作者开发的一些中 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式
在之前的系列文章中聊过分布式事务的一种实现方案,即通过在集群中暴露actor服务来实现分布式事务的本地原子化.但是actor服务本身有其特殊性,场景上并不通用.所以今天来讲讲分布式事务实现方案之sag ...
- [c语言]运算符的优先级与结合性
c语言中运算符的优先级和结合性常常被人混淆一谈,本文目的在于简单谈谈两者的区别.本文举几个简单的例子说明,这些运算符也特别常用. 首先要明白的是:优先级决定表达式中各种不同的运算符起作用的优先次序:而 ...
- 通过shell脚本批量操作mysql数据库
创建建表语句 ============================================= 学生表:Student(Sno,Sname,Ssex,Sage,Sdept) ------(学 ...