ASP.NET Core Identity 实战(4)授权过程
这篇文章我们将一起来学习 Asp.Net Core 中的(注:这样描述不准确,稍后你会明白)授权过程
前情提要
在之前的文章里,我们有提到认证和授权是两个分开的过程,而且认证过程不属于Identity。同样授权过程也不属于Identity,授权过程放在Identity系列中将的原因和认证过程一样——和成员系统放在一起容易理解。
动手做
在弄清的是授权过程在哪里发生的之前,我们先来动手写一写授权的代码,如果了解策略授权,那么你可以快速浏览过这部分
打开之前创建的项目,添加一个名为Demo的控制器,控制器代码如下:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace IdentityDemo.Controllers
{
[Produces("application/json")]
[Route("api/demo")]
public class DemoController : Controller
{
[Authorize]
[HttpGet]
public object Get()
{
return new
{
User.Identity.Name,
User.Identity.IsAuthenticated
略...
用之前注册的账户登录系统,
访问/api/demo
,你将得到如下结果:
{
"name": "jbl-2011@163.com",
"isAuthenticated": true
}
然后退出登录,再次访问/api/demo
,那么将会跳转到登陆页面,在这个过程中Authorize
特性起到了至关重要的作用,接下来去掉Authorize特性,重复上两个操作,未登录的结果将是:
{
"name": null,
"isAuthenticated": false
}
通过这两个小例子,我们很容易就能推断出Authorize特性拦截了没有登陆的用户,等等,是Authorize特性拦截了请求吗?
授权过程的发生地
很显然,不是Authorize特性拦截了请求,特性只是标记了这个方法需要被授权才能访问,而真正拦截了请求的是——“Mvc 中间件”。Action是由Mvc执行的,Mvc执行时会确认Action上的Authorize特性,来确定是否要进行授权操作(成功授权可以访问,失败了会被阻止(比如跳转到登陆)),以及如何授权(动物园例子中,第二个门卫根据切实的情况决定),也就是自定义授权(角色等等)。
另外,如果我们只是简单的为 Action方法打上[Authorize]
标记,那么它的默认行为就是验证IsAuthenticated是否是true,也就是在认证环节(Authentication 中间件)是否通过了认证
现在,我们知道了两个点
- 认证过程 Authentication 发生在 Authentication 中间件中
- 授权过程 Authorization 发生在 Mvc中间件中
基于策略的灵活授权
在企业应用中最为常见的就是基于角色的授权,实现角色授权的方式有两种,一种是直接写在Authorize
特性上:
[Authorize(Roles = "admin,super-admin,")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()
不过这种方式,不推荐,因为这样的话我们就将“角色”和“Uri”的绑定“硬编码在代码里了”,在很多场景这显然不合适,所以接下来我们要介绍的基于策略的授权就允许我们自定义授权逻辑,这样就灵活多了
基于策略Policy的授权
我们假设我们的授权规则是要求和上方代码片段实现相同效果,即用户具有角色“admin”或者角色“super-admin”,我们来逐步实现这个目标:
第一步在 DI 中注册一个用于我们需要的 policy
services.AddAuthorization(options =>
{
options.AddPolicy("role-policy", policy =>
{
policy.AddRequirements(new RoleRequirement("admin","super-admin"));
});
});
我们为该策略指定了一个名字role-policy
,并且指定了这个策略的需求条件,需求条件主要是为了设置策略的初始值,我们可以在策略注册时更改需求条件从而灵活控制授权。
接下来我们来编写 RoleRequirement
public class RoleRequirement : IAuthorizationRequirement
{
public IEnumerable<string> Roles { get; }
public RoleRequirement(params string[] roles)
{
Roles = roles ?? throw new ArgumentNullException(nameof(roles));
略...
那我们的 RoleRequirement
主要实现的功能就是确定要包含的角色,因为要包含的角色是在构造函数中确定的,那么我们就将角色授权的逻辑(稍后介绍的Handler)和具体授权的数据分开了。
然后我们来实现RoleRequirement
对应的处理程序:
public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
{
foreach (var item in requirement.Roles)
{
if (context.User.IsInRole(item))
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
context.Fail();
return Task.CompletedTask;
略...
这个处理器的工作十分简单就是验证当前用户是否在任意一个由RoleRequirement
指定的角色中。在这里context.Succeed(requirement);
指示授权成功,而授权失败一般不需要调用 context.Fail();
因为对于这个需求还可能有其它处理器进行处理,而此例中调用 context.Fail();
可以确保授权失败,因为RoleRequirement
的处理器只有一个,所以这样做是没有问题的。
要注意的是刚刚提到的,我们已经将角色授权的逻辑(稍后介绍的Handler)和具体授权的数据分开了。
因为RoleHandler
并不清楚要求用户有哪些角色,RoleHandler
只知道如何去验证用户含有哪些角色,而具体要求用户含有哪些角色,是由 RoleRequirement
来决定的,这符合关注点分离和单一职责这两个编程概念。
再然后,我们要将刚刚写好的RoleHandler
注册进Di
services.AddSingleton<IAuthorizationHandler, RoleHandler>();
最后一步,更换原来的Attribute:
// [Authorize(Roles = "admin,super-admin,")]
[Authorize(Policy ="role-policy")]
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()
现在,一个最基本的基于策略的授权就完成了。
本文中的示例较为简单,也并没有使用全部的授权特性,更详细的使用方法参考资料很多,本文也就不多做介绍。
另外你可以参考ASP.NET Core中基于策略的授权来学习更过关于策略授权的内容
授权时指定AuthenticationScheme
指定AuthenticationScheme的代码类似这样:
// [Authorize(Roles = "admin,super-admin,")]
[Authorize(AuthenticationSchemes ="jwt"/*注意,这里的名字取决于你添加AuthenticationHandler时的名字*/, Policy ="role-policy")] [HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test()
在上一篇博客ASP.NET Core Identity 实战(3)认证过程中提到,在Authentication中间件中可以放置多个Handler,而有一个是默认激活的,那么剩下的是被动调用的,现在我们的情况就是由我们在Authorize特性中去挑选一个Handler来执行,例如我们在Authentication中间件上放置两个Handler——CookieAuthenticationHandler和JwtAuthenticationHandler,并经CookieAuthenticationHandler指定为默认,那么我们想经由Jwt认证时怎么办?
这里有一个重要问题就是:当HttpContext流过Authentication中间件后才到Mvc中间件,而Mvc在确认Action指定的AuthenticationHandler时,Authentication过程已经结束了。
那这是怎么做到的呢?
还记的HttpContext中有一个扩展方法叫AuthenticateAsync
,作为HttpContext的扩展方法也就意味着,我们可以在任何时候调用它进行认证操作。
namespace Microsoft.AspNetCore.Authentication
{
public static class AuthenticationHttpContextExtensions
{
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context);
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme);
略...
看它的第二个重载,它是指定了 AuthenticationScheme的名字的,所以在Mvc中间件探查到Attribute指定了AuthenticationScheme时,就会重新挑选指定的AuthenticationHandler再次对请求进行认证
授权的发生地——AuthorizationFilter
在旧的Asp.Net时代,我们知道MvcFilter这个东西,现在它仍然在,如果你不了解它,我建议你稍作了解,建议参考官方文档
正如这一节的标题,授权发生在Microsoft.AspNetCore.Mvc.Authorization.AuthorizationFilter
中,授权的逻辑类似这样:
先进行认证
如果指定了scheme,那么重新认证,如果没有,则使用之前 Authentication中间件的授权结果:
public virtual async Task<AuthenticateResult> Microsoft.AspNetCore.Authorization.Policy.PolicyEvaluator.AuthenticateAsync(AuthorizationPolicy policy, HttpContext context)
{
if (policy.AuthenticationSchemes != null && policy.AuthenticationSchemes.Count > 0)
{
ClaimsPrincipal newPrincipal = null;
foreach (var scheme in policy.AuthenticationSchemes)
{
var result = await context.AuthenticateAsync(scheme);
if (result != null && result.Succeeded)
{
newPrincipal = SecurityHelper.MergeUserPrincipal(newPrincipal, result.Principal);
}
}
if (newPrincipal != null)
{
context.User = newPrincipal;
return AuthenticateResult.Success(new AuthenticationTicket(newPrincipal, string.Join(";", policy.AuthenticationSchemes)));
}
else
{
context.User = new ClaimsPrincipal(new ClaimsIdentity());
return AuthenticateResult.NoResult();
}
}
return (context.User?.Identity?.IsAuthenticated ?? false)
? AuthenticateResult.Success(new AuthenticationTicket(context.User, "context.User"))
: AuthenticateResult.NoResult();
}
这里面值得再次深入探讨的是 context.AuthenticateAsync(scheme)
,这是在 HttpAbstractions项目中的扩展方法,它的实现是:
public static Task<AuthenticateResult> AuthenticateAsync(this HttpContext context, string scheme) =>
context.RequestServices.GetRequiredService<IAuthenticationService>().AuthenticateAsync(context, scheme);
IAuthenticationService
我们在 Authentication中间件中也见过,Authentication中间件也是使用了IAuthenticationService
,之前的文章有提到过,这也再次证明了单一原则职责,身份认证中间件负责在管道中认证,而认证本身并非是和身份认证中间件捆绑的,上一篇博客ASP.NET Core Identity 实战(3)认证过程的最后有认证的源代码
再进行授权
授权总共分三步
- 拿到IAuthorizeHandler的实例(前面我们写了一个)(可能一个或者多个)
- 执行授权(每个Handler都会进行授权)
- 没了
这部分代码还是很简单的:
public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> 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(GetUserNameForLogging(user));
}
else
{
_logger.UserAuthorizationFailed(GetUserNameForLogging(user));
}
return result;
}
这里面和我们在项目中写的代码有关就是IAuthorizeHandler的实例,在本文中,我们写了一个RoleHandler
到此,授权过程就结束了,另外一些就是边边角角的知识点,比如授权之后如何操作,这些不难,就不再文中赘述了

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
年入50万的人是怎样工作的
1 小A,某外企部门经理,管理50多号人。 每天早上大约9点半到公司,每天估计开会(电话会议和现场会议)时间有6个小时,和领导和客户沟通时间估计2个小时,剩下的就是写ppt,邮件和跟进度了。一般来说,周末如果能有一天闲下来就不错了。 2 小B,某大厂架构师,技术牛人,分布式架构5年+经验,对于分布式系统里的反向代理,mq,数据库集群等组件有资深的实践经验。 每天平均开会时间+解决问题的时间,估计有6个小时,一般是下班后才有时间干自己的活,平时不怎么写代码,而是出方案,具体来说就是根据业务系统的现状设计组件,开发组件时遇到问题,得及时解决。而且需要手机一直开机,有问题得及时解决。 需要说明的是小B是资深架构,还没做到技术总监级别,如果能做到负责某个产品的首席架构或技术总监,年入估计要70万+了。 3 小C,有自己的公司,平时帮人出解决方案,卖自己公司的产品,顺带做监理,培训等等,反正什么活都干,平时经常在飞机上,不定时需要和别人去谈项目。而且,从谈客户到实施到问题解决到后继维护,大多都是自己解决。 4 小D,做培训的,培训内容涵盖PMP,数据库,AI等,做大班培训和...
-
下一篇
关于Spring Cloud的核心特性
SOA和微服务的区别 其实服务化架构已经可以解决大部分企业的需求了,那么我们为什么要研究微服务呢?先说说它们的区别; 微服务架构强调业务系统需要彻底的组件化和服务化,一个组件就是一个产品,可以独立对外提供服务微服务不再强调传统SOA架构里面比较重的ESB企业服务总线微服务强调每个微服务都有自己独立的运行空间,包括数据库资源。微服务架构本身来源于互联网的思路,因此组件对外发布的服务强调了采用HTTP Rest API的方式来进行微服务的切分粒度会更小总结:微服务架构是 SOA 架构思想的一种扩展,更加强调服务个体的独立性、拆分粒度更小。 为什么考虑Spring Cloud Spring Cloud来源于Spring,质量、稳定性、持续性都可以得到保证Spirng Cloud天然支持Spring Boot,更加便于业务落地。Spring Cloud发展非常的快,从16年开始接触的时候相关组件版本为1.x,到现在将要发布2.x系列Spring Cloud是Java领域最适合做微服务的框架。相比于其它框架,Spring Cloud对微服务周边环境的支持力度最大。对于中小企业来讲,使用门槛较低。...
相关文章
文章评论
共有0条评论来说两句吧...