Asp.Net Core--自定义基于策略的授权
翻译如下:
在封面下,角色授权和声明授权使用需求,需求的处理程序和预配置的策略。 这些构建块允许您在代码中表示授权评估,从而允许更丰富,可重用和容易测试的授权结构。
授权策略由一个或多个需求组成,并在应用程序启动时作为授权服务配置的一部分注册,在Startup.cs文件中的ConfigureServices中。
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthorization(options => { options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); } });
在这里您可以看到一个“Over21”策略创建有一个要求,最小年龄,作为参数传递到需求。
通过指定策略名称,使用Authorize属性应用策略,例如:
[Authorize(Policy="Over21")] public class AlcoholPurchaseRequirementsController : Controller { public ActionResult Login() { } public ActionResult Logout() { } }
Requirements(条件/要求)
授权requirement是策略可用于评估当前用户主体的数据参数的集合。 在我们的最低年龄政策中,要求我们有一个单一参数,即最低年龄。 要求必须实现IAuthorizationRequirement。 这是一个空的,标记接口。 参数化的最低年龄要求可以如下实现:
public class MinimumAgeRequirement : IAuthorizationRequirement { public MinimumAgeRequirement(int age) { MinimumAge = age; } protected int MinimumAge { get; set; } }
一个requirement不需要具有数据或属性。
授权处理程序
授权处理程序负责评估requirement的任何属性。 授权处理程序必须根据提供的AuthorizationContext来评估它们,以决定是否允许授权。 一个requirement可以有多个处理器。 处理程序必须继承AuthorizationHandler <T>,其中T是它处理的requirement。
最小年龄处理程序可能如下所示:
public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement> { protected override Task HandleRequirementAsync(AuthorizationContext context, MinimumAgeRequirement requirement) { if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com")) { // .NET 4.x -> return Task.FromResult(0); return Task.CompletedTask; } var dateOfBirth = Convert.ToDateTime(context.User.FindFirst( c => c.Type == ClaimTypes.DateOfBirth && c.Issuer == "http://contoso.com").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; } }
在上面的代码中,我们首先查看当前用户主体是否具有由我们知道和信任的发行方发布的出生日期索赔日期。 如果索赔失踪,我们不能授权,所以我们返回。 如果我们有一个索赔,我们计算出用户的年龄,如果他们满足最低年龄通过的要求,那么授权已经成功。 一旦授权成功,我们调用context.Succeed()在作为参数成功的需求中传递。
处理程序必须在配置期间在服务集合中注册,例如:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddAuthorization(options => { options.AddPolicy("Over21", policy => policy.Requirements.Add(new MinimumAgeRequirement(21))); }); services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>(); }
每个处理程序通过使用services.AddSingleton <AuthorizationHandler,YourHandlerClass>();添加到服务集合。 传入你的处理程序类。
处理程序需要返回什么?
你可以在我们的处理程序示例中看到Handle()方法没有返回值,那么我们如何指示成功或失败?
处理程序通过调用context.Succeed(授权需求要求)来指示成功,传递已成功验证的需求。
处理程序不需要一般处理故障,因为相同需求的其他处理程序可能会成功。
为了保证失败,即使其他处理程序为一个需求成功,调用context.Fail。
无论你在处理程序中调用什么,当策略需要该需求时,将调用一个需求的所有处理程序。 这允许需求具有副作用,例如日志记录,即使在另一个处理程序中调用了context.Fail(),它也总是会发生。
为什么需要多个处理程序?
如果您希望评估基于OR基础,则为单个需求实现多个处理程序。 例如,微软的门只能用钥匙卡打开。 如果你把钥匙卡留在家里,接待员打印一张临时贴纸,为你打开门。 在这种情况下,您将有一个单独的要求,EnterBuilding,但多个处理程序,每个检查单个要求。
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 && c.Issuer == "http://microsoftsecurity")) { context.Succeed(requirement); } 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 && c.Issuer == "https://microsoftsecurity")) { // We'd also check the expiration date on the sticker. context.Succeed(requirement); } return Task.CompletedTask; } }
现在,假设当策略评估输入构建要求时注册两个处理程序,如果任一处理程序成功,则策略评估将成功。
使用Func来实现策略
这里可以是其中完成策略在代码中简单表示的情况。 在使用RequireAssertion策略构建器配置策略时,可以简单地提供Func <AuthorizationHandlerContext,bool>。
例如,以前的BadgeEntryHandler可以被重写如下:
services.AddAuthorization(options => { options.AddPolicy("BadgeEntry", policy => policy.RequireAssertion(context => context.User.HasClaim(c => (c.Type == ClaimTypes.BadgeId || c.Type == ClaimTypes.TemporaryBadgeId) && c.Issuer == "https://microsoftsecurity")); })); } }
在处理程序中访问MVC请求上下文
您必须在授权处理程序中实现的Handle方法有两个参数:AuthorizationContext和要处理的要求。 框架(如MVC或Jabbr)可以向AuthorizationContext的Resource属性添加任何对象,以传递额外信息。
例如,MVC在resource属性中传递一个Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext的实例,该属性用于访问HttpContext,RouteData和MVC提供的所有内容。
Resource属性的使用是特定于框架的。 使用资源属性中的信息将限制您的授权策略到特定框架。 您应该使用as关键字转换Resource属性,然后检查转换是否成功,以确保您的代码在其他框架上运行时不会与InvalidCastExceptions崩溃;
var mvcContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext; if (mvcContext != null) { // Examine MVC specific things like routing data. }