经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段。在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥有Admin的角色,姓名是否叫XXX等等。本章就来介绍一下 ASP.NET Core 的授权系统的简单使用。
目录
在ASP.NET 4.x中,我们通常使用
过滤器来进行授权,它可以作用在Controller和Action上面,也可以添加到全局过滤器中。而在ASP.NET Core中也有一个
- Authorize
特性(但不是过滤器),用法类似:
- Authorize
- [Authorize] // Controller级别
- public class SampleDataController : Controller
- {
- [Authorize] // Action级别
- public IActionResult SampleAction()
- {
- }
- }
在ASP.NET 4.x中,我们最常用的另一个特性便是
,用来设置某个Controller或者Action跳过授权,它在 ASP.NET Core 中同样适用:
- AllowAnonymous
- [Authorize]
- public class AccountController : Controller
- {
- [AllowAnonymous]
- public ActionResult Login()
- {
- }
- public ActionResult Logout()
- {
- }
- }
如上,
Action便不再需要授权,同样,在 ASP.NET Core 中提供了一个统一的
- Login
接口,在授权逻辑中都是通过该接口来判断是否跳过授权验证的。
- IAllowAnonymous
- public interface IAllowAnonymous
- {
- }
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
- public class AllowAnonymousAttribute : Attribute, IAllowAnonymous
- {
- }
上面提到,在 ASP.NET Core 中,
不再是一个MVC中的
- AuthorizeAttribute
了,而只是一个简单的实现了
- Filter
接口的Attribute:
- IAuthorizeData
- public interface IAuthorizeData
- {
- string Policy { get; set; }
- string Roles { get; set; }
- string AuthenticationSchemes { get; set; }
- }
- [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
- public class AuthorizeAttribute : Attribute, IAuthorizeData
- {
- public AuthorizeAttribute() { }
- public AuthorizeAttribute(string policy)
- {
- Policy = policy;
- }
- public string Policy { get; set; }
- public string Roles { get; set; }
- public string AuthenticationSchemes { get; set; }
- }
记得第一次在ASP.NET Core中实现自定义授权时,按照以前的经验,直接继承自
,然后准备重写
- AuthorizeAttribute
方法,结果懵逼了。然后在MVC的源码中,苦苦搜寻
- OnAuthorization
的踪迹,却毫无所获,后来才注意到它实现了
- AuthorizeAttribute
接口,该接口才是认证的源头,而Authorize特性只是认证信息的载体,并不包含任何逻辑。
- IAuthorizeData
中定义的
- IAuthorizeData
,
- Policy
,
- Roles
三个属性分别代表着 ASP.NET Core 授权系统中的三种授权方式。
- AuthenticationSchemes
基于角色的授权,我们都比较熟悉,使用方式如下:
- [Authorize(Roles = "Admin")] // 多个Role可以使用,分割
- public class SampleDataController : Controller
- {
- ...
- }
基于角色的授权的逻辑与ASP.NET 4.x类似,都是使用我在《初识认证》中介绍的
方法来实现的。
- IsInRole
对于AuthenticationScheme我在前面几章也都介绍过,比如Cookie认证默认使用的AuthenticationScheme就是
,在JwtBearer认证中,默认的Scheme就是
- Cookies
。
- Bearer
当初在学习认证时,还在疑惑,如何在使用Cookie认证的同时又支持Bearer认证呢?因为在认证中只能设置一个Scheme来执行,当看到这里豁然开朗,后面会详细介绍。
- [Authorize(AuthenticationSchemes = "Cookies")] // 多个Scheme可以使用,分割
- public class SampleDataController : Controller
- {
- ...
- }
当我们的应用程序中,同时使用了多种认证Scheme时,AuthenticationScheme授权就非常有用,在该授权模式下,会通过
重新获取Claims。
- context.AuthenticateAsync(scheme)
在ASP.NET Core中,重新设计了一种更加灵活的授权方式:基于策略的授权,也是授权的核心。
在使用基于策略的授权时,首先要定义授权策略,而授权策略本质上就是对Claims的一系列断言。
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc();
- services.AddAuthorization(options =>
- {
- options.AddPolicy("EmployeeOnly", policy => policy.RequireClaim("EmployeeNumber"));
- });
- }
如上,我们定义了一个名称为
的授权策略,它要求用户的Claims中必须包含类型为
- EmployeeOnly
的Claim。
- EmployeeNumber
其实,基于角色的授权和基于Scheme的授权,只是一种语法上的便捷,最终都会生成授权策略,后文会详解介绍。
然后便可以在
特性中通过
- Authorize
属性来指定授权策略:
- Policy
- [Authorize(Policy = "EmployeeOnly")]
- public class SampleDataController : Controller
- {
- }
授权策略的定义使用了
扩展方法,我们来看看它的源码:
- AddAuthorization
- public static class AuthorizationServiceCollectionExtensions
- {
- public static IServiceCollection AddAuthorization(this IServiceCollection services)
- {
- services.TryAdd(ServiceDescriptor.Transient<IAuthorizationService, DefaultAuthorizationService>());
- services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());
- services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());
- services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>());
- services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());
- services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());
- return services;
- }
- public static IServiceCollection AddAuthorization(this IServiceCollection services, Action<AuthorizationOptions> configure)
- {
- services.Configure(configure);
- return services.AddAuthorization();
- }
- }
首先,是对授权进行配置的
,然后在DI系统中注册了几个核心对象的默认实现,我们一一来看。
- AuthorizationOptions
对于Options模式,大家应该都比较熟悉了,
是添加和获取授权策略的入口点:
- AuthorizationOptions
- public class AuthorizationOptions
- {
- private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase);
- // 在上一个策略验证失败后,是否继续执行下一个授权策略
- public bool InvokeHandlersAfterFailure { get; set; } = true;
- public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
- public void AddPolicy(string name, AuthorizationPolicy policy)
- {
- PolicyMap[name] = policy;
- }
- public void AddPolicy(string name, Action<AuthorizationPolicyBuilder> configurePolicy)
- {
- var policyBuilder = new AuthorizationPolicyBuilder();
- configurePolicy(policyBuilder);
- AddPolicy(name,policyBuilder.Build());
- }
- public AuthorizationPolicy GetPolicy(string name)
- {
- return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null;
- }
- }
首先是一个
字典,我们定义的策略都保存在其中,
- PolicyMap
方法只是简单的将策略添加到该字典中,而其
- AddPolicy
属性表示默认策略,初始值为:“已认证用户”。
- DefaultPolicy
在
中主要涉及到
- AuthorizationOptions
和
- AuthorizationPolicy
两个对象。
- AuthorizationPolicyBuilder
在 ASP.NET Core 中,授权策略具体表现为一个
对象:
- AuthorizationPolicy
- public class AuthorizationPolicy
- {
- public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> authenticationSchemes) {}
- public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
- public IReadOnlyList<string> AuthenticationSchemes { get; }
- public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
- {
- return Combine((IEnumerable<AuthorizationPolicy>)policies);
- }
- public static AuthorizationPolicy Combine(IEnumerable<AuthorizationPolicy> policies)
- {
- foreach (var policy in policies)
- {
- builder.Combine(policy);
- }
- return builder.Build();
- }
- public static async Task<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> authorizeData)
- {
- foreach (var authorizeDatum in authorizeData)
- {
- any = true;
- var useDefaultPolicy = true;
- if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
- {
- policyBuilder.Combine(await policyProvider.GetPolicyAsync(authorizeDatum.Policy));
- useDefaultPolicy = false;
- }
- var rolesSplit = authorizeDatum.Roles?.Split(',');
- if (rolesSplit != null && rolesSplit.Any())
- {
- policyBuilder.RequireRole(rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim()));
- useDefaultPolicy = false;
- }
- var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
- if (authTypesSplit != null && authTypesSplit.Any())
- {
- foreach (var authType in authTypesSplit)
- {
- if (!string.IsNullOrWhiteSpace(authType))
- {
- policyBuilder.AuthenticationSchemes.Add(authType.Trim());
- }
- }
- }
- if (useDefaultPolicy)
- {
- policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
- }
- }
- return any ? policyBuilder.Build() : null;
- }
- }
如上,
方法通过调用
- Combine
来完成授权策略的合并,而
- AuthorizationPolicyBuilder
则是将我们上面介绍的
- CombineAsync
转换为授权策略,因此上面说基于角色/Scheme的授权本质上都是基于策略的授权。
- IAuthorizeData
对于
属性,我们在前几章介绍认证时经常看到,用来表示我们使用哪个认证Scheme来获取用户的Claims,如果指定多个,则会合并它们的Claims,其实现下一章再来介绍。
- AuthenticationSchemes
而
属性则是策略的核心了,每一个Requirement都代表一个授权条件,我们就先来了解一下它。
- Requirements
Requirement使用
接口来表示:
- IAuthorizationRequirement
- public interface IAuthorizationRequirement
- {
- }
IAuthorizationRequirement接口中并没有任何成员,在 ASP.NET Core 中内置了一些常用的实现:
中将其设置为默认策略。
- AuthorizationOptions
来判断是否包含预期的Role的授权策略。
- ClaimsPrincipal.IsInRole
来判断是否包含预期的Name的授权策略。
- ClaimsPrincipal.Identities.Name
其逻辑也都非常简单,我就不再一一介绍,只展示一下
的代码片段:
- RolesAuthorizationRequirement
- public class RolesAuthorizationRequirement: AuthorizationHandler < RolesAuthorizationRequirement > ,
- IAuthorizationRequirement {
- public IEnumerable < string > AllowedRoles {
- get;
- }
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement) {...
- if (requirement.AllowedRoles.Any(r = >context.User.IsInRole(r))) {
- context.Succeed(requirement);
- }
- return Task.CompletedTask;
- }
- }
其
表示允许授权通过的角色,而它还实现了
- AllowedRoles
接口,用来完成授权的逻辑。
- IAuthorizationHandler
- public interface IAuthorizationHandler
- {
- Task HandleAsync(AuthorizationHandlerContext context);
- }
AuthorizationRequirement并不是一定要实现
接口,后文会详细介绍。
- IAuthorizationHandler
在上面已经多次用到
,它提供了一系列创建
- AuthorizationPolicyBuilder
的快捷方法:
- AuthorizationPolicy
- public class AuthorizationPolicyBuilder {
- public AuthorizationPolicyBuilder(params string[] authenticationSchemes);
- public AuthorizationPolicyBuilder(AuthorizationPolicy policy);
- public IList < IAuthorizationRequirement > Requirements {
- get;
- set;
- }
- public IList < string > AuthenticationSchemes {
- get;
- set;
- }
- public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes);
- public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements);
- public AuthorizationPolicyBuilder RequireAssertion(Func < AuthorizationHandlerContext, bool > handler);
- public AuthorizationPolicyBuilder RequireAssertion(Func < AuthorizationHandlerContext, Task < bool >> handler) {
- Requirements.Add(new AssertionRequirement(handler));
- return this;
- }
- public AuthorizationPolicyBuilder RequireAuthenticatedUser() {
- Requirements.Add(new DenyAnonymousAuthorizationRequirement());
- return this;
- }
- public AuthorizationPolicyBuilder RequireClaim(string claimType);
- public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] requiredValues);
- public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable < string > requiredValues) {
- Requirements.Add(new ClaimsAuthorizationRequirement(claimType, requiredValues));
- return this;
- }
- public AuthorizationPolicyBuilder RequireRole(params string[] roles);
- public AuthorizationPolicyBuilder RequireRole(IEnumerable < string > roles) {
- Requirements.Add(new RolesAuthorizationRequirement(roles));
- return this;
- }
- public AuthorizationPolicyBuilder RequireUserName(string userName) {
- Requirements.Add(new NameAuthorizationRequirement(userName));
- return this;
- }
- public AuthorizationPolicy Build();
- public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy);
- }
在上面介绍的几个Requirement,除了
外,都有对应的快捷添加方法,由于
- OperationAuthorizationRequirement
并不属于基于资源的授权,所以不在这里,其用法留在其后续章节再来介绍。
- OperationAuthorizationRequirement
整个授权策略的内容也就这么多,并不复杂,整个结构大致如下:
在上一小节,我们探索了一下授权策略的源码,现在就来实战一下。
我们使用
可以很容易的在策略定义中组合我们需要的Requirement:
- AuthorizationPolicyBuilder
- public void ConfigureServices(IServiceCollection services)
- {
- var commonPolicy = new AuthorizationPolicyBuilder().RequireClaim("MyType").Build();
- services.AddAuthorization(options =>
- {
- options.AddPolicy("User", policy => policy
- .RequireAssertion(context => context.User.HasClaim(c => (c.Type == "EmployeeNumber" || c.Type == "Role")))
- );
- options.AddPolicy("Employee", policy => policy
- .RequireRole("Admin")
- .RequireUserName("Alice")
- .RequireClaim("EmployeeNumber")
- .Combine(commonPolicy));
- });
- }
如上,如果需要,我们还可以定义一个公共的策略对象,然后在策略定义中直接将其合并进来。
当内置的Requirement不能满足我们的需求时,我们也可以很容易的定义自己的Requirement:
- public class MinimumAgeRequirement: AuthorizationHandler < NameAuthorizationRequirement > ,
- IAuthorizationRequirement {
- public MinimumAgeRequirement(int minimumAge) {
- MinimumAge = minimumAge;
- }
- public int MinimumAge {
- get;
- private set;
- }
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement) {
- if (context.User != null && context.User.HasClaim(c = >c.Type == ClaimTypes.DateOfBirth) {
- var dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c = >c.Type == ClaimTypes.DateOfBirth).Value);
- int calculatedAge = DateTime.Today.Year - dateOfBirth.Year;
- if (dateOfBirth > DateTime.Today.AddYears( - calculatedAge)) {
- calculatedAge--;
- }
- if (calculatedAge >= requirement.MinimumAge) {
- context.Succeed(requirement);
- }
- }
- return Task.CompletedTask;
- }
- }
然后就可以直接在
中使用了:
- AddPolicy
- services.AddAuthorization(options =>
- {
- options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21)));
- });
我们自定义的 Requirement 若想得到 ASP.NET Core 授权系统的执行,除了上面示例中的实现
接口外,也可以单独定义AuthorizationHandler,这样可以更好的使用DI系统,并且还可以定义多个Handler,下面就来演示一下。
- IAuthorizationHandler
授权策略中的多个Requirement,它们属于 & 的关系,只用全部验证通过,才能最终授权成功。但是在有些场景下,我们可能希望一个授权策略可以适用多种情况,比如,我们进入公司时需要出示员工卡才可以被授权进入,但是如果我们忘了带员工卡,可以去申请一个临时卡,同样可以授权成功:
- public class EnterBuildingRequirement : IAuthorizationRequirement
- {
- }
- public class BadgeEntryHandler : AuthorizationHandler<EnterBuildingRequirement>
- {
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EnterBuildingRequirement requirement)
- {
- if (context.User.HasClaim(c => c.Type == ClaimTypes.BadgeId)
- {
- context.Succeed(requirement);
- }
- else
- {
- // context.Fail();
- }
- return Task.CompletedTask;
- }
- }
- public class HasTemporaryStickerHandler : AuthorizationHandler<EnterBuildingRequirement>
- {
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EnterBuildingRequirement requirement)
- {
- if (context.User.HasClaim(c => c.Type == ClaimTypes.TemporaryBadgeId)
- {
- context.Succeed(requirement);
- }
- return Task.CompletedTask;
- }
- }
如上,我们定义了两个Handler,但是想让它们得到执行,还需要将其注册到DI系统中:
- services.AddSingleton<IAuthorizationHandler, BadgeEntryHandler>();
- services.AddSingleton<IAuthorizationHandler, HasTemporaryStickerHandler>();
此时,在我们的应该程序中使用
的授权时,将会依次执行这两个Handler。而在上面介绍
- EnterBuildingRequirement
时,提到它还有一个
- AuthorizationOptions
属性,在这里就派上用场了,只有其为
- InvokeHandlersAfterFailure
时(默认为True),才会在当前 AuthorizationHandler 授权失败时,继续执行下一个 AuthorizationHandler。
- true
在上面的示例中,我们使用
将授权结果设置为成功,而失败时并没有做任何标记,正常情况下都是这样做的。但是如果需要,我们可以通过调用
- context.Succeed(requirement)
方法显式的将授权结果设置为失败,那么,不管其他 AuthorizationHandler 是成功还是失败,最终结果都将是授权失败。
- context.Fail()
ASP.NET Core 授权策略是一种非常强大、灵活的权限验证方案,能够满足大部分的授权场景。通过本文对授权策略的详细介绍,我想应该能够灵活的使用基于策略的授权了,但是授权策略到底是怎么执行的呢?在下一章中,就来完整的探索一下 ASP.NET Core 授权系统的执行流程。
来源: http://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html