基于声明和基于策略的授权 ASP.NET Core 2.1
介绍
授权是确定用户是否能够访问系统资源的过程。在我之前的文章中,我已经解释了基于角色的授权。身份会员系统允许我们与用户映射一个或多个角色;根据角色,我们可以做授权。在本文中,我将说明如何根据策略和声明进行授权。
基于声明的授权
声明是用户数据,由受信任的源颁发。如果我们使用基于令牌的身份验证,则生成令牌的服务器可能会在令牌中添加声明。索赔可以包含任何类型的数据,例如“加入日期”,“出生日期”,“电子邮件”等。根据用户拥有的声明,系统提供对页面的访问权限,这称为基于声明的授权。例如,如果用户有“出生日期”声明,系统将提供对页面的访问权限。简而言之,基于声明的授权会检查声明的值,并允许根据声明的值访问系统资源。
为了通过示例进行演示,我创建了 2 个用户,并将一些声明标识与该用户相关联。我通过使用以下代码实现了这一点。
- public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
- {
- ....
- ....
- app.UseAuthentication();
- app.UseMvc(routes =>
- {
- routes.MapRoute(
- name: "default",
- template: "{controller=Home}/{action=Index}/{id?}");
- });
- CreateUserAndClaim(serviceProvider).Wait();
- }
- private async Task CreateUserAndClaim(IServiceProvider serviceProvider)
- {
- var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
- IdentityUser user = await UserManager.FindByEmailAsync("jignesh@gmail.com");
- if (user == null)
- {
- user = new IdentityUser()
- {
- UserName = "jignesh@gmail.com",
- Email = "jignesh@gmail.com",
- };
- await UserManager.CreateAsync(user, "Test@123");
- }
- var claimList = (await UserManager.GetClaimsAsync(user)).Select(p => p.Type);
- if (!claimList.Contains("DateOfJoing")){
- await UserManager.AddClaimAsync(user, new Claim("DateOfJoing", "09/25/1984"));
- }
- if (!claimList.Contains("IsAdmin")){
- await UserManager.AddClaimAsync(user, new Claim("IsAdmin", "true"));
- }
- IdentityUser user2 = await UserManager.FindByEmailAsync("rakesh@gmail.com");
- if (user2 == null)
- {
- user2 = new IdentityUser()
- {
- UserName = "rakesh@gmail.com",
- Email = "rakesh@gmail.com",
- };
- await UserManager.CreateAsync(user2, "Test@123");
- }
- var claimList1 = (await UserManager.GetClaimsAsync(user2)).Select(p => p.Type);
- if (!claimList.Contains("IsAdmin"))
- {
- await UserManager.AddClaimAsync(user2, new Claim("IsAdmin", "false"));
- }
- }
基于声明的授权可以通过创建策略来完成;即,创建并注册声明声明要求的政策。简单类型的声明策略仅检查声明是否存在,但对于高级声明,我们可以检查用户声明及其值。我们还可以为声明检查分配多个值。
在以下示例中,我创建了检查 2 个声明的用户授权的策略:一个用于“加入日期”,另一个用于“IsAdmin”。这里的“加入日期”是一种简单的索赔类型;即,它仅检查声明是否存在,而“IsAdmin”声明检查其值。
- public void ConfigureServices(IServiceCollection services)
- {
- ....
- ....
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- services.AddAuthorization(options =>
- {
- options.AddPolicy("IsAdminClaimAccess", policy => policy.RequireClaim("DateOfJoing"));
- options.AddPolicy("IsAdminClaimAccess", policy => policy.RequireClaim("IsAdmin", "true"));
- });
- }
- [Authorize(Policy = "IsAdminClaimAccess")]
- public IActionResult TestMethod1()
- {
- return View("MyPage");
- }
我们还可以将多个策略应用于控制器或操作。要授予访问权限,必须通过所有策略。
- [Authorize(Policy = "IsAdminClaimAccess")]
- [Authorize(Policy = "NonAdminAccess")]
- public IActionResult TestMethod2()
- {
- return View("MyPage");
- }
- private async Task CreateUserAndClaim(IServiceProvider serviceProvider)
- {
- var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();
- var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();
- //Added Roles
- var roleResult = await RoleManager.FindByNameAsync("Administrator");
- if (roleResult == null)
- {
- roleResult = new IdentityRole("Administrator");
- await RoleManager.CreateAsync(roleResult);
- }
- var roleClaimList = (await RoleManager.GetClaimsAsync(roleResult)).Select(p => p.Type);
- if(!roleClaimList.Contains("ManagerPermissions"))
- {
- await RoleManager.AddClaimAsync(roleResult, new Claim("ManagerPermissions", "true"));
- }
- IdentityUser user = await UserManager.FindByEmailAsync("jignesh@gmail.com");
- if (user == null)
- {
- user = new IdentityUser()
- {
- UserName = "jignesh@gmail.com",
- Email = "jignesh@gmail.com",
- };
- await UserManager.CreateAsync(user, "Test@123");
- }
- await UserManager.AddToRoleAsync(user, "Administrator");
- ....
- ....
- ....
- ....
- }
与上述代码相同,我们可以为基于角色的声明创建策略,并使用 Authorize 属性将其应用于控制器或操作方法。
- public void ConfigureServices(IServiceCollection services)
- {
- ...
- ...
- services.AddAuthorization(options =>
- {
- ...
- ...
- options.AddPolicy("RoleBasedClaim", policy => policy.RequireClaim("ManagerPermissions", "true"));
- });
- }
- [Authorize(Policy = "RoleBasedClaim")]
- public IActionResult TestMethod3()
- {
- return View("MyPage");
- }
基于策略的授权
.NET 核心框架允许我们创建授权策略。我们可以使用预配置的策略,也可以根据我们的要求创建自定义策略。
在基于角色的授权和基于声明的授权(请参阅上一节)中,我们使用预配置的策略,如“要求声明”和“要求角色”。该策略包含一个或多个要求,并在添加授权服务配置中注册。
授权要求
要求是收集可用于评估用户主体的数据。若要创建要求,该类必须实现接口 I身份验证要求,即空接口。该要求不包含任何数据和评估机制。
例
在以下示例中,我创建了一个组织花费的最短时间要求。
- public class MinimumTimeSpendRequirement: IAuthorizationRequirement
- {
- public MinimumTimeSpendRequirement(int noOfDays)
- {
- TimeSpendInDays = noOfDays;
- }
- protected int TimeSpendInDays { get; private set; }
- }
授权处理程序
授权处理程序包含需求属性的评估机制。处理程序必须根据授权上下文评估要求属性,并确定是否允许用户访问系统资源。一个要求可能有多个处理程序。授权处理程序必须继承自授权处理程序<T>类;这里的T是一种需求类。
例
在下面的示例代码中,我为要求“最小时间支出要求”创建了一个处理程序。此处理程序首先查找加入声明的日期(加入日期)。如果用户不存在此声明,我们可以将其标记为未经授权的请求。如果用户有声明,则我们计算用户在组织内花费了多少天。如果这满足授权服务中传递的要求,则用户有权访问。我有呼叫上下文。成功(),这意味着用户满足了所有要求。
- public class MinimumTimeSpendHandler : AuthorizationHandler<MinimumTimeSpendRequirement>
- {
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumTimeSpendRequirement requirement)
- {
- if (!context.User.HasClaim(c => c.Type == "DateOfJoining"))
- {
- return Task.FromResult(0);
- }
- var dateOfJoining = Convert.ToDateTime(context.User.FindFirst(
- c => c.Type == "DateOfJoining").Value);
- double calculatedTimeSpend = (DateTime.Now.Date - dateOfJoining.Date).TotalDays;
- if (calculatedTimeSpend >= requirement.TimeSpendInDays)
- {
- context.Succeed(requirement);
- }
- return Task.FromResult(0);
- }
- }
处理程序注册
使用授权的句柄必须在服务集合中注册。我们可以通过 "services.AddSingleton<IAuthorizationHandler, ourHandlerClass>()" 方法传递处理程序类来添加到服务集合:
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
- services.AddAuthorization(options =>
- {
- ...
- ...
- options.AddPolicy("Morethan365DaysClaim", policy => policy.Requirements.Add(new MinimumTimeSpendRequirement(365)));
- }
- services.AddSingleton<IAuthorizationHandler, MinimumTimeSpendHandler>();
- }
处理程序不返回任何值,因此处理程序通过调用上下文来指示成功。成功(要求)方法,这里我们传递的任何要求都已成功验证。如果我们不调用上下文。成功方法,处理程序自动失败,或者我们可以调用上下文。失败() 方法。
要求的多个处理程序
在某些情况下,我们需要根据 OR 条件来评估需求,我们可以为单个需求实现多个处理程序。例如,我们有一个要求,例如,如果用户在组织中花费至少 365 天,或者某个用户在 HR 部门的用户中,则该页面可由用户访问。在这种情况下,我们有一个访问页面的要求,而不是多个处理程序来验证单个要求。
例
- using Microsoft.AspNetCore.Authorization;
- using System;
- using System.Threading.Tasks;
- namespace ClaimBasedPolicyBasedAuthorization.Policy
- {
- public class PageAccessRequirement : IAuthorizationRequirement
- {
- }
- public class TimeSpendHandler : AuthorizationHandler<PageAccessRequirement>
- {
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PageAccessRequirement requirement)
- {
- if (!context.User.HasClaim(c => c.Type == "DateOfJoining"))
- {
- return Task.FromResult(0);
- }
- var dateOfJoining = Convert.ToDateTime(context.User.FindFirst(
- c => c.Type == "DateOfJoining").Value);
- double calculatedTimeSpend = (DateTime.Now.Date - dateOfJoining.Date).TotalDays;
- if (calculatedTimeSpend >= 365)
- {
- context.Succeed(requirement);
- }
- return Task.FromResult(0);
- }
- }
- public class RoleCheckerHandler : AuthorizationHandler<PageAccessRequirement>
- {
- protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PageAccessRequirement requirement)
- {
- if (!context.User.HasClaim(c => c.Type == "IsHR"))
- {
- return Task.FromResult(0);
- }
- var isHR = Convert.ToBoolean(context.User.FindFirst(c => c.Type == "IsHR").Value);
- if (isHR)
- {
- context.Succeed(requirement);
- }
- return Task.FromResult(0);
- }
- }
- }
- public void ConfigureServices(IServiceCollection services)
- {
- ....
- ....
- ....
- services.AddAuthorization(options =>
- {
- ....
- ....
- options.AddPolicy("AccessPageTestMethod5", policy => policy.Requirements.Add(new PageAccessRequirement()));
- });
- ...
- ...
- services.AddSingleton<IAuthorizationHandler, TimeSpendHandler>();
- services.AddSingleton<IAuthorizationHandler, RoleCheckerHandler>();
- }
使用 RequireAssertion 策略生成器方法,我们可以为策略添加简单的表达式,而无需和处理程序。上面提到的代码示例可以重写如下
- services.AddAuthorization(options =>
- {
- ...
- ....
- options.AddPolicy("AccessPageTestMethod6",
- policy => policy.RequireAssertion(context =>
- context.User.HasClaim(c =>
- (c.Type == "IsHR" && Convert.ToBoolean(context.User.FindFirst(c2 => c2.Type == "IsHR").Value)) ||
- (c.Type == "DateOfJoining" && (DateTime.Now.Date - Convert.ToDateTime(context.User.FindFirst(c2 => c2.Type == "DateOfJoining").Value).Date).TotalDays >= 365))
- ));
- });
句柄要求异步方法包含两个参数:“授权上下文”和“要求”。某些框架(如 MVC)允许将任何对象添加到授权上下文的 Resource 属性中,以传递额外的信息。此 Resource 属性特定于框架,因此我们可以强制转换上下文。资源对象到适当的上下文并访问资源,
- var myContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;
- if (myContext != null)
- {
- // Examine MVC specific item.
- var controllerName = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)myContext.ActionDescriptor).ControllerName;
- }
总结
基于声明的授权允许我们根据其他特征(例如用户名,加入日期,员工,其他信息等)来验证用户。使用另一种授权(如基于角色的授权)可能是不可能的。基于声明的授权可以通过使用预配置的策略基于策略的授权来实现。我们可以使用预配置的策略,也可以根据我们的授权要求创建自定义策略。
可以从此处的 GitHub 链接查看或下载源代码。
浙公网安备 33010602011771号