您描述的方法似乎是正确的。一切都取决于您的要求。
假设您的应用程序中有多个功能,如果您选择使用角色,则属于该功能的代码必须每次检查用户是否处于一组特定的角色中才能使用该功能。当功能和角色增长时,这种方法变得非常难以管理,因为您必须考虑将角色组合到每个单独的功能中。在本例中,只有PowerUser或Administrator的用户才能进行管理操作X。现在,这看起来简单明了,但是,如果您添加一个新角色ALittleBitMorePowerful,它也可以执行X 操作的User,会发生什么情况。为了达到这个结果,您必须审查所有内容并更改检查(这意味着重新测试整个事情)。
如果您使用声明 CanPerformX 设计了功能 X,那么您将引入一个抽象层:您的代码将不关心用户的角色,而只会检查自己的声明。如果您曾经修改声明与用户的关联方式,您的有效代码将不会改变(这最终意味着没有引入正式的回归)。
角色被设计为广泛,而声明被设计为细粒度。但是,正如您在链接中看到的那样,您可能会认为一个角色是“大角色”,或者是一个“小角色”。
我发布了我的代码的一小段摘录,它支持自定义角色但固定声明。
定义声明
internal static class PolicyClaims
{
public const string AdministratorClaim = @"http://myorganization/2019/administrator";
public const string Operation1Claim = @"http://myorganization/2019/op1";
public const string Operation2Claim = @"http://myorganization/2019/op2";
public const string ObtainedClaim = @"true";
}
定义策略
internal static class Policies
{
public const string RequireAdministrator = "RequireAdministrator";
public const string RequireOp1 = "RequireOp1";
public const string RequireOp2 = "RequireOp2";
public const string AlwaysDeny = "AlwaysDeny";
public static void ConfigurePolicies(IServiceCollection services)
{
services.AddAuthorization(options => options.AddPolicy(RequireAdministrator, policy => policy.RequireClaim(PolicyClaims.AdministratorClaim)));
services.AddAuthorization(options => options.AddPolicy(RequireOp1, policy => policy.RequireClaim(PolicyClaims.Operation1Claim)));
services.AddAuthorization(options => options.AddPolicy(RequireOp2, policy => policy.RequireClaim(PolicyClaims.Operation2Claim)));
services.AddAuthorization(options => options.AddPolicy(AlwaysDeny, policy => policy.RequireUserName("THIS$USER\n\r\t\0cannot be created")));
}
}
在Startup.RegisterServices注册政策
Policies.ConfigurePolicies(services);
在您对用户进行身份验证的位置,根据您的逻辑决定需要添加哪些声明(省略了一些部分以专注于概念)
[AllowAnonymous]
[Route("api/authentication/authenticate")]
[HttpPost()]
public async Task<IActionResult> Authenticate([FromBody] LoginModel model)
{
if (ModelState.IsValid)
{
var user = m_UserManager.Users.FirstOrDefault(x => x.UserName == model.UserName);
if (user == null)
{
...
}
else
{
var result = await m_SignInManager.CheckPasswordSignInAsync(user, model.Password, false);
if (result.Succeeded)
{
var handler = new JwtSecurityTokenHandler();
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
new Claim(ClaimTypes.Name, model.UserName)
}),
Expires = DateTime.UtcNow.AddHours(2),
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(InstanceSettings.JWTKey), SecurityAlgorithms.HmacSha256Signature)
};
var roles = await m_UserManager.GetRolesAsync(user);
AddClaims(tokenDescriptor, roles);
var token = handler.CreateToken(tokenDescriptor);
var tokenString = handler.WriteToken(token);
return ...
}
else
{
...
}
}
}
return ...
}
private static void AddClaims(SecurityTokenDescriptor tokenDescriptor, IList<string> roles)
{
if (roles.Any(x => string.Equals(Constants.AdministratorRoleName, x, StringComparison.OrdinalIgnoreCase)))
{
tokenDescriptor.Subject.AddClaim(new Claim(PolicyClaims.AdministratorClaim, PolicyClaims.ObtainedClaim));
tokenDescriptor.Subject.AddClaim(new Claim(PolicyClaims.Operation1Claim, PolicyClaims.ObtainedClaim));
tokenDescriptor.Subject.AddClaim(new Claim(PolicyClaims.Operation2Claim, PolicyClaims.ObtainedClaim));
}
... query the database and add each claim with value PolicyClaims.ObtainedClaim ...
}
最后,您可以使用这些策略来保护您的代码:
[Authorize(Policy = Policies.RequireAdministrator)]
[HttpPost("execute")]
public async Task<IActionResult> ExecuteOperation([FromBody] CommandModel model)
{
...
}
请注意,在这种方法中,我将某些声明硬编码给管理员,因为我想阻止管理员删除某些声明。但是,这不是强制性的。