这篇文章咱们将一块儿来学习 Asp.Net Core 中的(注:这样描述不许确,稍后你会明白)受权过程html
在以前的文章里,咱们有提到认证和受权是两个分开的过程,并且认证过程不属于Identity。一样受权过程也不属于Identity,受权过程放在Identity系列中将的缘由和认证过程同样——和成员系统放在一块儿容易理解。编程
在弄清的是受权过程在哪里发生的以前,咱们先来动手写一写受权的代码,若是了解策略受权,那么你能够快速浏览过这部分json
打开以前建立的项目,添加一个名为Demo的控制器,控制器代码以下:api
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
,你将获得以下结果:mvc
{ "name": "jbl-2011@163.com", "isAuthenticated": true }
而后退出登陆,再次访问/api/demo
,那么将会跳转到登录页面,在这个过程当中Authorize
特性起到了相当重要的做用,接下来去掉Authorize特性,重复上两个操做,未登陆的结果将是:app
{ "name": null, "isAuthenticated": false }
经过这两个小例子,咱们很容易就能推断出Authorize特性拦截了没有登录的用户,等等,是Authorize特性拦截了请求吗?async
很显然,不是Authorize特性拦截了请求,特性只是标记了这个方法须要被受权才能访问,而真正拦截了请求的是——“Mvc 中间件”。Action是由Mvc执行的,Mvc执行时会确认Action上的Authorize特性,来肯定是否要进行受权操做(成功受权能够访问,失败了会被阻止(好比跳转到登录)),以及如何受权(动物园例子中,第二个门卫根据切实的状况决定),也就是自定义受权(角色等等)。ide
另外,若是咱们只是简单的为 Action方法打上[Authorize]
标记,那么它的默认行为就是验证IsAuthenticated是不是true,也就是在认证环节(Authentication 中间件)是否经过了认证函数
如今,咱们知道了两个点学习
在企业应用中最为常见的就是基于角色的受权,实现角色受权的方式有两种,一种是直接写在Authorize
特性上:
[Authorize(Roles = "admin,super-admin,")] [HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Test()
不过这种方式,不推荐,由于这样的话咱们就将“角色”和“Uri”的绑定“硬编码在代码里了”,在不少场景这显然不合适,因此接下来咱们要介绍的基于策略的受权就容许咱们自定义受权逻辑,这样就灵活多了
咱们假设咱们的受权规则是要求和上方代码片断实现相同效果,即用户具备角色“admin”或者角色“super-admin”,咱们来逐步实现这个目标:
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的代码相似这样:
// [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再次对请求进行认证
在旧的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)认证过程的最后有认证的源代码
受权总共分三步
这部分代码仍是很简单的:
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
到此,受权过程就结束了,另一些就是边边角角的知识点,好比受权以后如何操做,这些不难,就再也不文中赘述了