asp.net core 3.x 授权默认流程
一、前言
接上一篇《asp.net core 3.x 授权中的概念》,本篇看看asp.net core默认授权的流程。从两个方面来看整个授权系统是怎么运行的:启动阶段的配置、请求阶段中间件的处理流程。
由于asp.net core 3.x目前使用终结点路由,因此授权框架可以用于所有asp.net web项目类型,比如:webapi mvc razorpages...。但本篇只以MVC为例
二、核心概念关系图
三、启动阶段的配置
主要体现为3点
- 注册相关服务
- 配置授权选项对象AuthorizationOptions
- 注册授权中间件
3.1、注册相关服务和选项配置
在mvc项目Startup.ConfigreServices中services.AddControllersWithViews(); (MvcServiceCollectionExtensions)用来向依赖注入框架注册各种mvc相关服务。其中会调用services.AddAuthorization(选项)扩展方法(PolicyServiceCollectionExtensions)注册授权相关服务,此扩展方法内部还会调用两个扩展方法,这里不再深入。
这里主要需要搞懂2个问题:
- 方法传入的选项
- 具体注册了哪些服务
3.1.1、授权选项AuthorizationOptions
AddAuthorization扩展方法的参数是Action<AuthorizationOptions>类型的,这是asp.net core中典型的选项模型,将来某个地方需要时,直接注入此选项对象,那时依赖注入容器会使用此委托对这个选项对象赋值。所以我们在启动时可以通过此对象来对授权框架进行配置。
最最重要的是我们可以在这里配置全局授权策略列表,参考上图的右侧中间部分,源码不多,注意注释。
//代表授权系统的全局选项对象,里面最最核心的就是存储着全局授权策略
public class AuthorizationOptions
{
//存储全局授权策略(AuthorizationPolicy),key是策略唯一名,方便将来获取
private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
//授权验证时,将遍历所有授权处理器(Authorization)逐个进行验证,若某个发生错误是否立即终止后续的授权处理器的执行
public bool InvokeHandlersAfterFailure { get; set; } = true;
//默认授权策略,拒绝匿名访问
public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
//若将来授权检查时没有找到合适的授权策略,默认授权策略也是空的情况下会回退使用此策略
public AuthorizationPolicy FallbackPolicy { get; set; }
//添加全局策略
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;
}
//添加全局策略,同时可以对此策略进行配置
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();
}
//获取指定名称的策略
public AuthorizationPolicy GetPolicy(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
} return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
}
}
AuthorizationOptions
举个栗子:
services.AddControllersWithViews();
services.AddRazorPages();
services.AddAuthorization(opt =>
{
opt.AddPolicy("授权策略1", builer => {
builer.RequireRole("admin", "manager");
builer.AddAuthenticationSchemes("cookie", "google");
//继续配置....
});
opt.AddPolicy("授权策略2", builer => {
builer.RequireRole("teacher");
builer.AddAuthenticationSchemes("wechat", "qq");
//继续配置....
});
});
3.1.2、具体注册了哪些服务:
- 策略评估器IPolicyEvaluator:名字有点诡异。默认实现PolicyEvaluator,授权中间件委托它来实现身份验证和授权处理,它内部会调用AuthorizationService,进而执行所有授权处理器AuthorizationHandler
- 授权服务IAuthorizationService:上一篇有说,不详述了,默认实现DefaultAuthorizationService,除了授权中间件会调用它来进行授权处理,我们业务代码中也可以调用它来做授权验证,比如:针对资源的特殊授权
- 授权策略提供器IAuthorizationPolicyProvider:默认实现DefaultAuthorizationPolicyProvider,可以通过它来获取指定名称的授权,它就是从全局授权策略列表里去找,也就是上面说的AuthorizationOptions中
- 授权处理器提供器IAuthorizationHandlerProvider:默认实现DefaultAuthorizationHandlerProvider,通过它来获取系统中所有的授权处理器,其实就是从IOC容器中获取
- 授权评估器IAuthorizationEvaluator:默认实现DefaultAuthorizationEvaluator,授权处理器AuthorizationHandler在执行完授权后,结果是存储在AuthorizationHandlerContext中的,这里的评估器只是根据AuthorizationHandlerContext创建一个授权结果AuthorizationResult,给了我们一个机会来加入自己的代码而已
- 授权处理器上下文对象的工厂IAuthorizationHandlerContextFactory:默认实现DefaultAuthorizationHandlerContextFactory,授权处理器AuthorizationHandler在授权时需要传入AuthorizationHandlerContext(上面说了授权完成后的结果也存储在里面)。所以在执行授权处理器之前需要构建这个上下文对象,就是通过这个工厂构建的,主要的数据来源就是 当前 或者 指定的 授权策略AuthorizationPolicy
- 授权处理器IAuthorizationHandler:默认实现PassThroughAuthorizationHandler。授权的主要逻辑在授权处理器中定义,授权服务在做授权时会遍历系统所有的授权处理器逐一验证,而验证往往需要用到授权依据,PassThroughAuthorizationHandler比较特殊,它会看授权依据是否已经实现了IAuthorizationHandler,如过是,则直接把授权依据作为授权处理器进行执行。因为多数情况下一个授权处理器类型是专门针对某种授权依据定义的。
这些接口都是扩展点,就问你怕不怕?当然绝大部分时候我们不用管,默认的就足够用了。
3.2、注册授权中间件
主要注意的位置的为题,必须在路由和身份验证之后。
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
扩展方法内部注册了AuthorizationMiddleware
四、请求阶段的处理流程
如果你对mvc稍有经验,就晓得在一个Action上使用[Authorize]就可以实施授权,现在我们假设我们在默认mvc项目中的HomeController定义如下Action,并应用授权标签
[Authorize(Policy = "p1")]//使用全局授权策略中的"p1"进行授权判断
[Authorize(AuthenticationSchemes = "google")]//只允许使用google身份验证登录的用户访问
[Authorize(Roles = "manager")]//只允许角色为manager的访问
public IActionResult Privacy()
{
return View();
}
4.1、授权中间件AuthorizationMiddleware
核心步骤如下:
- 从当前请求拿到终结点
- 通过终结点拿到其关联的IAuthorizeData集合
- 调用AuthorizationPolicy.CombineAsync根据IAuthorizeData集合创建一个复合型策略,此策略就是本次用来做授权检查的策略,也就是文章中多次提到的当前这略
- 从IOC容器中获取策略评估器对上面得到的策略进行身份验证,多种身份验证得到的用户证件信息会合并进HttpContext.User
- 若Action上应用了IAllowAnonymous,则放弃授权检查(为毛不早点做这步?)
- 通过策略评估器对策略进行授权检查,注意这里的参数,传入身份验证评估结果和将终结点作为资源
- 若授权评估要求质询,则遍历策略所有的身份验证方案,进行质询,若策略里木有身份验证方案则使用默认身份验证方案进行质询
- 若授权评估拒绝就直接调用身份验证方案进行拒绝
步骤1、2得益于asp.net core 3.x的终结点路由,我们可以在进入MVC框架前就拿到Action及其之上应用的各种Atrribute,从而得到我们对当前授权策略定制所需要的数据
步骤3会根据得到IAuthorizeData集合(AuthorizeAttribute实现了IAuthorizeData,它不再是一个过滤器)创建当前准备用来做授权的策略。授权策略中 “身份验证方案列表” 和 “授权依据列表”,就是通过这里的标签来的。具体来说:
- [Authorize(Policy = "p1")]:会通过“p1”去全局授权策略(AuthorizationOptions对象中)拿到对应的策略,然后与当前策略合并,也就是把“p1”策略中的身份验证方案列表、授权依据列表与当前策略合并。
- [Authorize(AuthenticationSchemes = "google")]:用来定制当前策略的“身份验证方案列表”,当然最终和上面说的合并,
- [Authorize(Roles = "manager")]:会创建一个RolesAuthorizationRequirement类型的授权依据加入到当前策略中
这些Attribute可以应用多次,最终都是来定制当前授权策略的。后续步骤都会依赖此授权策略。
步骤4中,若发现本次授权策略中定义了多个身份验证方案,则会注意进行身份验证,得到的多张证件会合并到当前用户HttpContext.User中,当然默认身份验证得到的用户信息也在其中。
上面步骤4、6是委托策略评估器PolicyEvaluator来完成的,往下看..
4.2、策略评估器PolicyEvaluator
核心任务就两个,身份验证、进行授权
4.2.1、AuthenticateAsync
若策略没有设置AuthenticationSchemes,则只判断下当前请求是否已做身份验证,若做了就返回成功
若策略设置了AuthenticationSchemes,则遍历身份验证方案逐个进行身份验证处理 context.AuthenticateAsync(scheme); ,将所有得到的用户标识重组成一个复合的用户标识。
4.2.2、AuthorizeAsync
调用IAuthorizationService进行权限判断,若成功则返回成功。
否则 若身份验证通过则 PolicyAuthorizationResult.Forbid() 直接通知身份验证方案,做拒绝访问处理;否则返回质询
所以授权检查的任务又交给了授权服务AuthorizationService
4.3、授权服务AuthorizationService
核心步骤如下:
- 通过IAuthorizationHandlerContextFactory创建AuthorizationHandlerContext,此上下文包含:授权依据(来源与当前授权策略) 当前用户(httpContext.User)和资源(当前终结点)
- 遍历所有授权处理器AuthorizationHandler,这些授权处理器是通过IAuthorizationHandlerProvider获取的,默认情况下是从IOC容器中获取的。逐个调用每个授权处理器执行授权检查
- 所有授权处理器执行验证后的结果还是存储在上面说的这个上下文对象AuthorizationHandlerContext中。回调授权评估器IAuthorizationEvaluator将这个上下文对象转换为授权结果AuthorizationResult
步骤2还会判断AuthorizationOptios.InvokeHandlersAfterFailure,来决定当某个处理器发生错误时,是否停止执行后续的授权处理器。
4.4、授权处理器AuthorizationHandler
前面讲过,默认只注册了一个PassThroughAuthorizationHandler授权处理器,它会遍历当前授权策略中实现了IAuthorizationHandler的授权依据(意思说这些对象既是授权处理器,也是授权依据)。直接执行它们。
public class PassThroughAuthorizationHandler : IAuthorizationHandler
{
public async Task HandleAsync(AuthorizationHandlerContext context)
{
foreach (var handler in context.Requirements.OfType<IAuthorizationHandler>())
{
await handler.HandleAsync(context);
}
}
}
以基于角色的授权依据RolesAuthorizationRequirement为例,它继承于AuthorizationHandler<RolesAuthorizationRequirement>,且实现IAuthorizationRequirement
public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
//省略部分代码...
public IEnumerable<string> AllowedRoles { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
{
if (context.User != null)
{
bool found = false;
if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
{
// Review: What do we want to do here? No roles requested is auto success?
}
else
{
found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
}
if (found)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
五、最后
可以感受到asp.net core 3.x目前的权限设计棒棒哒,默认的处理方式已经能满足大部分需求,即使有特殊需求扩展起来也非常简单,前面注册部分看到注册了各种服务,且都有默认实现,这些服务在授权检查的不同阶段被使用,如果有必要我们可以自定义实现某些接口来实现扩展。本篇按默认流程走了一波,最好先看前一篇。这时候再去看官方文档或源码应该更容易。日常开发使用其实参考官方文档就足够了,无非就是功能权限和数据权限,看情况也许不会写后续的应用或扩展篇了。
asp.net core 3.x 授权默认流程的更多相关文章
- ASP.NET Core 认证与授权[5]:初识授权
经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段.在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥 ...
- ASP.NET Core 认证与授权[6]:授权策略是怎么执行的?
在上一章中,详细介绍了 ASP.NET Core 中的授权策略,在需要授权时,只需要在对应的Controler或者Action上面打上[Authorize]特性,并指定要执行的策略名称即可,但是,授权 ...
- asp.net core 3.x 授权中的概念
前言 预计是通过三篇来将清楚asp.net core 3.x中的授权:1.基本概念介绍:2.asp.net core 3.x中授权的默认流程:3.扩展. 在完全没有概念的情况下无论是看官方文档还是源码 ...
- 聊聊 asp.net core 认证和授权
使用asp.net core 开发应用系统过程中,基本上都会涉及到用户身份的认证,及授权访问控制,因此了解认证和授权流程也相当重要,下面通过分析asp.net core 框架中的认证和授权的源码来分析 ...
- 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?
在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...
- Asp.Net Core 中IdentityServer4 授权原理及刷新Token的应用
一.前言 上面分享了IdentityServer4 两篇系列文章,核心主题主要是密码授权模式及自定义授权模式,但是仅仅是分享了这两种模式的使用,这篇文章进一步来分享IdentityServer4的授权 ...
- Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式
一.前言 上一篇我分享了一篇关于 Asp.Net Core 中IdentityServer4 授权中心之应用实战 的文章,其中有不少博友给我提了问题,其中有一个博友问我的一个场景,我给他解答的还不够完 ...
- ASP.NET Core 中jwt授权认证的流程原理
目录 1,快速实现授权验证 1.1 添加 JWT 服务配置 1.2 颁发 Token 1.3 添加 API访问 2,探究授权认证中间件 2.1 实现 Token 解析 2.2 实现校验认证 1,快速实 ...
- ASP.NET Core 认证与授权[1]:初识认证
在ASP.NET 4.X 中,我们最常用的是Forms认证,它既可以用于局域网环境,也可用于互联网环境,有着非常广泛的使用.但是它很难进行扩展,更无法与第三方认证集成,因此,在 ASP.NET Cor ...
随机推荐
- Springboot引入本地jar时打包
在项目的开发过程中有时我们需要引入我们本地的jar包,这些jar包没有存在maven仓库中 ,这时没有办法通过pom文件直接引入,在开发过程中我们可以通过add as library的方式,可以在开发 ...
- 19秦皇岛现场赛F题 dfs
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6736 如果环的边长为k,那么环的删边方案数是2k-1.如果链的边长为k,那么链的删边方案数是2k.环的 ...
- python爬虫——scrapy的使用
本文中的知识点: 安装scrapy scrapy的基础教程 scrapy使用代理 安装scrapy 由于小哥的系统是win7,所以以下的演示是基于windows系统.linux系统的话,其实命令都一样 ...
- Gitlab应用——系统管理
查看linux系统信息 查看日志 创建账号 选择regular,这是一个普通账号,点击“create user”账号创建完成 点击“User”,然后点击“New user”.使 ...
- HCNA 2017年01月26日
[Huawei]ping 127.0.0.1 PING 127.0.0.1: 56 data bytes, press CTRL_C to break Reply from 127.0.0.1: by ...
- n-tier waf 41 project 层真够多
ps: http://waf.codeplex.com/releases/view/618696
- 定义可选URL片段 定义自定义片段变量 精通ASP-NET-MVC-5-弗瑞曼
- 废旧手机改造之给你的手机安装win10系统
最近又开始琢磨把我这个即将退出的二手手机再利用一下 发现了一个不错的软件 先上图 是不是感觉逼格很高啊 点击下面链接即可下载使用 https://www.lanzous.com/i4gpsib 欢迎交 ...
- 创建dynamics CRM client-side (七) - 用JS 来控制Auto-Save
在我们的system setting里面, 我们可以设置打开/关闭 auto save的功能. 我们可以用js来控制auto-save this.formOnSave = function (exec ...
- [模板]线性递推+BM
暴力版本: #include<bits/stdc++.h> #define mod 998244353 using namespace std; typedef long long int ...