程序认证身份之后就是授权,授权也有很多种
三种:基于角色,基于声明, 基于策略
此项目的demo的git地址:https://github.com/xeekseven/AspNet-core-Example/tree/master/ANC-Authorize-CustomRequirement
场景
如果有这样的一个需求,一个管理系统,里面有多重角色,每个角色有多种权限,而且角色的权限是动态可调整了,一个用户多种角色,角色也是可调整了,这样一个: 权限(n)—(1) 角色 (n)— (1)用户 这样的一个需求,应该怎么实现?其实 ,自定义策略模式即可满足
前言
为何不使用简单策略?简单策略模式无非就是逻辑运算符比基于声明多了些,但是比较鸡肋,不如......直接上自定义策略
基于自定义策略授权
复杂类型的授权,好处在于动态调整,当然就要自己写策略提供器,也就是根据不同的参数来生成不同的策略,然后还需要自己重新实现策略的handler
- 先定义一个权限策略PermissionRequirement,包含一些属性
public class PermissionRequirement : IAuthorizationRequirement {
public string PermissionName { get; }
public PermissionRequirement (string PermissionName) {
this.PermissionName = PermissionName;
}
}
- 再定义一个策略处理类:
//策略处理类
public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{
var role = context.User.FindFirst(c => c.Type == ClaimTypes.Role);
if (role != null)
{
var roleValue = role.Value;
var permissions = RolePermissionCache.GetPermissions(role.Value);
if (permissions.Contains(requirement.PermissionName))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
//权限动态缓存类 临时替代数据库
public class RolePermissionCache
{
//实际在数据库获取与配置
public static List<string> GetPermissions(string role){
switch(role){
case "Administrator":
return new List<string>(){ "Index","Privacy" };
case "Custom":
return new List<string>(){ "Index" };
}
return new List<string>();
}
}
- 还需要一个动态 new 策略声明的类,用来保证每次不同的参数对应的是不同属性值的一种策略:
internal class PermissionPolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "Permission";
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
}
public Task<AuthorizationPolicy> GetFallbackPolicyAsync()
{
return Task.FromResult(new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build());
}
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if(policyName.StartsWith(POLICY_PREFIX,System.StringComparison.OrdinalIgnoreCase)){
var policy = new AuthorizationPolicyBuilder();
policy.AddRequirements(new PermissionRequirement(policyName.Substring(POLICY_PREFIX.Length)));
return Task.FromResult(policy.Build());
}
return Task.FromResult<AuthorizationPolicy>(null);
}
}
- 最后需要一个特性来在控制器应用自定义的策略的特性:
internal class PermissionAuthorizeAttribute : AuthorizeAttribute
{
const string POLICY_PREFIX = "Permission";
public PermissionAuthorizeAttribute(string permissionName) => PermissionName = permissionName;
public string PermissionName
{
get
{
return PermissionName;
}
set
{
Policy = $"{POLICY_PREFIX}{value.ToString()}";
}
}
}
- 现在就在startup启动类中进行配置:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, PermissionRequirementHandler>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.AccessDeniedPath = new PathString("/Home/NotPermission");
options.LoginPath = new PathString("/Home/Login");
options.ExpireTimeSpan = TimeSpan.FromSeconds(10);
});
services.AddControllersWithViews();
services.AddRazorPages();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseCookiePolicy();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
}
- 终于,现在可以根据我们自己的需求来自定义每个控制器的策略了:
[PermissionAuthorize("Privacy")]
public IActionResult Privacy()
{
return View();
}
- 当然还有登录的代码:
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login(string username,string password)
{
var returnUrl = HttpContext.Request.Query["ReturnUrl"];
string roleType = "";
if(username == "admin"){
roleType = "Administrator";
}
else if(username == "custom"){
roleType = "Custom";
}
if((username == "admin" && password == "admin") || (username == "custom" && password == "custom")){
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name,username),
new Claim("Role",roleType),
new Claim(ClaimTypes.Role,roleType)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity), new AuthenticationProperties());
if (!string.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("/Home/Index");
}
if (!string.IsNullOrWhiteSpace(returnUrl))
{
return Redirect(returnUrl);
}
return Redirect("/Home/Login");
}
前一篇 —ASP.NET Core中的授权(2) — 基于声明: //www.greatytc.com/p/f96c181c34d9