新文章 网摘 文章 随笔 日记

基于声明和基于策略的授权 ASP.NET Core 2.1

介绍
 
授权是确定用户是否能够访问系统资源的过程。在我之前的文章中,我已经解释了基于角色的授权。身份会员系统允许我们与用户映射一个或多个角色;根据角色,我们可以做授权。在本文中,我将说明如何根据策略和声明进行授权。
 
 
基于声明的授权
 
声明是用户数据,由受信任的源颁发。如果我们使用基于令牌的身份验证,则生成令牌的服务器可能会在令牌中添加声明。索赔可以包含任何类型的数据,例如“加入日期”,“出生日期”,“电子邮件”等。根据用户拥有的声明,系统提供对页面的访问权限,这称为基于声明的授权。例如,如果用户有“出生日期”声明,系统将提供对页面的访问权限。简而言之,基于声明的授权会检查声明的值,并允许根据声明的值访问系统资源。
 
 
为了通过示例进行演示,我创建了 2 个用户,并将一些声明标识与该用户相关联。我通过使用以下代码实现了这一点。
 
  1. public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)  
  2. {  
  3.     ....  
  4.     ....  
  5.   
  6.     app.UseAuthentication();  
  7.   
  8.     app.UseMvc(routes =>  
  9.     {  
  10.         routes.MapRoute(  
  11.             name: "default",  
  12.             template: "{controller=Home}/{action=Index}/{id?}");  
  13.     });  
  14.     CreateUserAndClaim(serviceProvider).Wait();  
  15. }  
  16. private async Task CreateUserAndClaim(IServiceProvider serviceProvider)  
  17. {  
  18.     var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();  
  19.     IdentityUser user = await UserManager.FindByEmailAsync("jignesh@gmail.com");  
  20.   
  21.     if (user == null)  
  22.     {  
  23.         user = new IdentityUser()  
  24.         {  
  25.             UserName = "jignesh@gmail.com",  
  26.             Email = "jignesh@gmail.com",  
  27.         };  
  28.         await UserManager.CreateAsync(user, "Test@123");  
  29.     }  
  30.   
  31.     var claimList = (await UserManager.GetClaimsAsync(user)).Select(p => p.Type);  
  32.     if (!claimList.Contains("DateOfJoing")){  
  33.         await UserManager.AddClaimAsync(user, new Claim("DateOfJoing", "09/25/1984"));  
  34.     }  
  35.     if (!claimList.Contains("IsAdmin")){  
  36.         await UserManager.AddClaimAsync(user, new Claim("IsAdmin", "true"));  
  37.     }  
  38.   
  39.     IdentityUser user2 = await UserManager.FindByEmailAsync("rakesh@gmail.com");  
  40.   
  41.     if (user2 == null)  
  42.     {  
  43.         user2 = new IdentityUser()  
  44.         {  
  45.             UserName = "rakesh@gmail.com",  
  46.             Email = "rakesh@gmail.com",  
  47.         };  
  48.         await UserManager.CreateAsync(user2, "Test@123");  
  49.     }  
  50.     var claimList1 = (await UserManager.GetClaimsAsync(user2)).Select(p => p.Type);  
  51.     if (!claimList.Contains("IsAdmin"))  
  52.     {  
  53.         await UserManager.AddClaimAsync(user2, new Claim("IsAdmin", "false"));  
  54.     }  
  55. }  
 
 
基于声明的授权可以通过创建策略来完成;即,创建并注册声明声明要求的政策。简单类型的声明策略仅检查声明是否存在,但对于高级声明,我们可以检查用户声明及其值。我们还可以为声明检查分配多个值。
 
 
在以下示例中,我创建了检查 2 个声明的用户授权的策略:一个用于“加入日期”,另一个用于“IsAdmin”。这里的“加入日期”是一种简单的索赔类型;即,它仅检查声明是否存在,而“IsAdmin”声明检查其值。
 
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     ....  
  4.     ....  
  5.     services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);  
  6.     services.AddAuthorization(options =>  
  7.     {  
  8.         options.AddPolicy("IsAdminClaimAccess", policy => policy.RequireClaim("DateOfJoing"));  
  9.         options.AddPolicy("IsAdminClaimAccess", policy => policy.RequireClaim("IsAdmin", "true"));  
  10.     });  
  11. }  
我们可以将此策略应用于使用“策略”属性的授权属性。在这里,我们必须指定策略的名称。
 
  1. [Authorize(Policy = "IsAdminClaimAccess")]  
  2. public IActionResult TestMethod1()  
  3. {  
  4.     return View("MyPage");  
  5. }  
我们还可以将多个策略应用于控制器或操作。要授予访问权限,必须通过所有策略。
 
  1. [Authorize(Policy = "IsAdminClaimAccess")]  
  2. [Authorize(Policy = "NonAdminAccess")]  
  3. public IActionResult TestMethod2()  
  4. {  
  5.     return View("MyPage");  
  6. }  
在上面的示例中,我们将声明分配给用户,并通过创建策略进行授权。或者,也可以将声明分配给用户角色,因此使用此选项,整个用户组可以访问页面或资源。我在代码中进行了一些小的更改,该更改生成了用户、用户角色并向角色添加了声明。
 
  1. private async Task CreateUserAndClaim(IServiceProvider serviceProvider)  
  2. {  
  3.     var UserManager = serviceProvider.GetRequiredService<UserManager<IdentityUser>>();  
  4.     var RoleManager = serviceProvider.GetRequiredService<RoleManager<IdentityRole>>();  
  5.   
  6.     //Added Roles  
  7.     var roleResult = await RoleManager.FindByNameAsync("Administrator");  
  8.     if (roleResult == null)  
  9.     {  
  10.         roleResult = new IdentityRole("Administrator");  
  11.         await RoleManager.CreateAsync(roleResult);  
  12.     }  
  13.   
  14.     var roleClaimList = (await RoleManager.GetClaimsAsync(roleResult)).Select(p => p.Type);  
  15.     if(!roleClaimList.Contains("ManagerPermissions"))  
  16.     {  
  17.         await RoleManager.AddClaimAsync(roleResult, new Claim("ManagerPermissions", "true"));  
  18.     }  
  19.   
  20.     IdentityUser user = await UserManager.FindByEmailAsync("jignesh@gmail.com");  
  21.   
  22.     if (user == null)  
  23.     {  
  24.         user = new IdentityUser()  
  25.         {  
  26.             UserName = "jignesh@gmail.com",  
  27.             Email = "jignesh@gmail.com",  
  28.         };  
  29.         await UserManager.CreateAsync(user, "Test@123");  
  30.     }  
  31.     await UserManager.AddToRoleAsync(user, "Administrator");  
  32.     ....  
  33.     ....  
  34.     ....  
  35.     ....  
  36. }   
 
 
与上述代码相同,我们可以为基于角色的声明创建策略,并使用 Authorize 属性将其应用于控制器或操作方法。
 
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     ...  
  4.     ...  
  5.     services.AddAuthorization(options =>  
  6.     {  
  7.         ...  
  8.         ...  
  9.         options.AddPolicy("RoleBasedClaim", policy => policy.RequireClaim("ManagerPermissions", "true"));  
  10.     });  
  11. }  
 
  1. [Authorize(Policy = "RoleBasedClaim")]  
  2. public IActionResult TestMethod3()  
  3. {  
  4.     return View("MyPage");  
  5. }  
基于策略的授权
 
.NET 核心框架允许我们创建授权策略。我们可以使用预配置的策略,也可以根据我们的要求创建自定义策略。
 
 
在基于角色的授权和基于声明的授权(请参阅上一节)中,我们使用预配置的策略,如“要求声明”和“要求角色”。该策略包含一个或多个要求,并在添加授权服务配置中注册。
 
 
授权要求
 
要求是收集可用于评估用户主体的数据。若要创建要求,该类必须实现接口 I身份验证要求,即空接口。该要求不包含任何数据和评估机制。
 
 
在以下示例中,我创建了一个组织花费的最短时间要求。
 
  1. public class MinimumTimeSpendRequirement: IAuthorizationRequirement  
  2. {  
  3.     public MinimumTimeSpendRequirement(int noOfDays)  
  4.     {  
  5.         TimeSpendInDays = noOfDays;  
  6.     }  
  7.   
  8.     protected int TimeSpendInDays { get; private set; }  
  9. }  
授权处理程序
 
授权处理程序包含需求属性的评估机制。处理程序必须根据授权上下文评估要求属性,并确定是否允许用户访问系统资源。一个要求可能有多个处理程序。授权处理程序必须继承自授权处理程序<T>类;这里的T是一种需求类。
 
 
在下面的示例代码中,我为要求“最小时间支出要求”创建了一个处理程序。此处理程序首先查找加入声明的日期(加入日期)。如果用户不存在此声明,我们可以将其标记为未经授权的请求。如果用户有声明,则我们计算用户在组织内花费了多少天。如果这满足授权服务中传递的要求,则用户有权访问。我有呼叫上下文。成功(),这意味着用户满足了所有要求。
 
  1. public class MinimumTimeSpendHandler : AuthorizationHandler<MinimumTimeSpendRequirement>  
  2. {  
  3.     protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, MinimumTimeSpendRequirement requirement)  
  4.     {  
  5.         if (!context.User.HasClaim(c => c.Type == "DateOfJoining"))  
  6.         {  
  7.             return Task.FromResult(0);  
  8.         }  
  9.   
  10.         var dateOfJoining = Convert.ToDateTime(context.User.FindFirst(  
  11.             c => c.Type == "DateOfJoining").Value);  
  12.   
  13.         double calculatedTimeSpend = (DateTime.Now.Date - dateOfJoining.Date).TotalDays;  
  14.               
  15.         if (calculatedTimeSpend >= requirement.TimeSpendInDays)  
  16.         {  
  17.             context.Succeed(requirement);  
  18.         }  
  19.         return Task.FromResult(0);  
  20.     }  
  21. }  
处理程序注册
 
 
使用授权的句柄必须在服务集合中注册。我们可以通过 "services.AddSingleton<IAuthorizationHandler, ourHandlerClass>()"    方法传递处理程序类来添加到服务集合:
 
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.      services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);  
  4.      services.AddAuthorization(options =>  
  5.      {  
  6.         ...  
  7.         ...  
  8.         options.AddPolicy("Morethan365DaysClaim", policy => policy.Requirements.Add(new MinimumTimeSpendRequirement(365)));  
  9.      }  
  10.      services.AddSingleton<IAuthorizationHandler, MinimumTimeSpendHandler>();  
  11. }  

处理程序不返回任何值,因此处理程序通过调用上下文来指示成功。成功(要求)方法,这里我们传递的任何要求都已成功验证。如果我们不调用上下文。成功方法,处理程序自动失败,或者我们可以调用上下文。失败() 方法。
 
 
要求的多个处理程序
 
在某些情况下,我们需要根据 OR 条件来评估需求,我们可以为单个需求实现多个处理程序。例如,我们有一个要求,例如,如果用户在组织中花费至少 365 天,或者某个用户在 HR 部门的用户中,则该页面可由用户访问。在这种情况下,我们有一个访问页面的要求,而不是多个处理程序来验证单个要求。
 
 
 
  1. using Microsoft.AspNetCore.Authorization;  
  2. using System;  
  3. using System.Threading.Tasks;  
  4.   
  5. namespace ClaimBasedPolicyBasedAuthorization.Policy  
  6. {  
  7.     public class PageAccessRequirement : IAuthorizationRequirement  
  8.     {  
  9.     }  
  10.   
  11.     public class TimeSpendHandler : AuthorizationHandler<PageAccessRequirement>  
  12.     {  
  13.         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PageAccessRequirement requirement)  
  14.         {  
  15.             if (!context.User.HasClaim(c => c.Type == "DateOfJoining"))  
  16.             {  
  17.                 return Task.FromResult(0);  
  18.             }  
  19.   
  20.             var dateOfJoining = Convert.ToDateTime(context.User.FindFirst(  
  21.                 c => c.Type == "DateOfJoining").Value);  
  22.   
  23.             double calculatedTimeSpend = (DateTime.Now.Date - dateOfJoining.Date).TotalDays;  
  24.   
  25.             if (calculatedTimeSpend >= 365)  
  26.             {  
  27.                 context.Succeed(requirement);  
  28.             }  
  29.             return Task.FromResult(0);  
  30.         }  
  31.     }  
  32.     public class RoleCheckerHandler : AuthorizationHandler<PageAccessRequirement>  
  33.     {  
  34.         protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PageAccessRequirement requirement)  
  35.         {  
  36.             if (!context.User.HasClaim(c => c.Type == "IsHR"))  
  37.             {  
  38.                 return Task.FromResult(0);  
  39.             }  
  40.   
  41.             var isHR = Convert.ToBoolean(context.User.FindFirst(c => c.Type == "IsHR").Value);  
  42.   
  43.             if (isHR)  
  44.             {  
  45.                 context.Succeed(requirement);  
  46.             }  
  47.             return Task.FromResult(0);  
  48.         }  
  49.     }  
  50. }  
在这里,我们需要注册这两个处理程序。如果两个处理程序中的任何一个成功,则策略评估已成功。
 
  1. public void ConfigureServices(IServiceCollection services)  
  2. {  
  3.     ....  
  4.     ....  
  5.     ....  
  6.     services.AddAuthorization(options =>  
  7.     {  
  8.         ....  
  9.         ....  
  10.         options.AddPolicy("AccessPageTestMethod5", policy => policy.Requirements.Add(new PageAccessRequirement()));  
  11.     });  
  12.     ...  
  13.     ...  
  14.     services.AddSingleton<IAuthorizationHandler, TimeSpendHandler>();  
  15.     services.AddSingleton<IAuthorizationHandler, RoleCheckerHandler>();  
  16. }   
使用 RequireAssertion 策略生成器方法,我们可以为策略添加简单的表达式,而无需和处理程序。上面提到的代码示例可以重写如下
 
  1. services.AddAuthorization(options =>  
  2. {  
  3.     ...  
  4.     ....  
  5.     options.AddPolicy("AccessPageTestMethod6",  
  6.         policy => policy.RequireAssertion(context =>  
  7.             context.User.HasClaim(c =>  
  8.             (c.Type == "IsHR" && Convert.ToBoolean(context.User.FindFirst(c2 => c2.Type == "IsHR").Value)) ||  
  9.             (c.Type == "DateOfJoining" && (DateTime.Now.Date - Convert.ToDateTime(context.User.FindFirst(c2 => c2.Type == "DateOfJoining").Value).Date).TotalDays >= 365))  
  10.             ));  
  11. });  
我们是否可以在处理程序中访问请求上下文?
 
 
句柄要求异步方法包含两个参数:“授权上下文”和“要求”。某些框架(如 MVC)允许将任何对象添加到授权上下文的 Resource 属性中,以传递额外的信息。此 Resource 属性特定于框架,因此我们可以强制转换上下文。资源对象到适当的上下文并访问资源,
 
  1. var myContext = context.Resource as Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext;  
  2.   
  3. if (myContext != null)  
  4. {  
  5.     // Examine MVC specific item.  
  6.     var controllerName = ((Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor)myContext.ActionDescriptor).ControllerName;  
  7. }  
总结
 
基于声明的授权允许我们根据其他特征(例如用户名,加入日期,员工,其他信息等)来验证用户。使用另一种授权(如基于角色的授权)可能是不可能的。基于声明的授权可以通过使用预配置的策略基于策略的授权来实现。我们可以使用预配置的策略,也可以根据我们的授权要求创建自定义策略。
 
 
可以从此处的 GitHub 链接查看或下载源代码。
 
posted @ 2022-10-24 10:10  岭南春  阅读(129)  评论(0)    收藏  举报