澄清身份授权:将声明用作角色,角色和声明或角色声明

Mor*_*goZ 9 c# asp.net-authorization asp.net-identity asp.net-core-mvc

我从ASP.NET身份的声明授权开始,如果我需要在我的应用程序中使用"角色"概念,我想澄清继续使用它们的方式.

注意:我对此真的很新,所以所有的概念都在脑子里浮现,请善待,任何概念的进一步澄清/更正将非常感激.

1.-假设,我需要管理员和用户角色的"角色"概念,所以我的第一个想法是将声明添加到ApplicationUser:

user.Claims.Add(new IdentityUserClaim<string> { ClaimType = "Role", ClaimValue = "Admin" });
Run Code Online (Sandbox Code Playgroud)

*"用户"是一个ApplicationUser.

但后来我读到它已经由框架完成,因为它有一些预定义的声明类型,所以上面的代码可能是:

user.Claims.Add(new IdentityUserClaim<string> { ClaimType = ClaimTypes.Role, ClaimValue = "Admin" });
Run Code Online (Sandbox Code Playgroud)

这种方法是否正确?或者我应该使用"旧"角色概念并向用户添加角色,如:

await _roleManager.CreateAsync(new IdentityRole("Admin"));    
await _userManager.AddToRoleAsync(user, "Admin");
Run Code Online (Sandbox Code Playgroud)

2.-现在假设我将角色定义为声明,我如何检查它们的自动化?我的意思是,它会起作用吗?

[Authorize(Roles = "Admin")]
Run Code Online (Sandbox Code Playgroud)

或者我应该包含一个政策声明来检查角色声明?

/* In startup ConfigureServices method*/
options.AddPolicy("IsAdmin", policy => {
                policy.RequireClaim(ClaimTypes.Role, "Admin");
                });

...

/*In a controller class*/
[Authorize(Policy = "IsAdmin")]
<controller here>
Run Code Online (Sandbox Code Playgroud)

3.-现在,存储我的自定义索赔的正确方法是什么?我的意思是,ASP.NET的ClaimTypes类只是一堆const string值,所有关于Claims的示例代码都将它们存储在类似的类中:

public static class ClaimData
{
    public static List<string> AdminClaims { get; set; } = new List<string>
                                                        {
                                                            "Add User",
                                                            "Edit User",
                                                            "Delete User"
                                                        };
}
Run Code Online (Sandbox Code Playgroud)

这可以吗?

最后的注意事项.-我还在互联网上看到了"角色声明"的概念,该博客文章对此进行了解释:http://benfoster.io/blog/asp-net-identity-role-claims

那是什么?如果我不够困惑,现在有第三种方式授权用户.这是使用角色作为声明的更好方法吗?

Pie*_*ier 2

您描述的方法似乎是正确的。一切都取决于您的要求。

想象一下,您的应用程序中有多个功能,如果您选择使用角色,则属于该功能的代码必须每次检查用户是否处于特定的角色集中才能使用该功能。当功能和角色增长时,这种方法变得非常难以管理,因为您必须考虑将角色组合到每个功能中。X本例中,只有为PowerUser或时,用户才可以进行管理操作Administrator。现在,这看起来简单明了,但是,如果您添加一个新角色 ,ALittleBitMorePowerfulUser也可以执行该X操作的角色,会发生什么。为了达到这个结果,您必须检查所有内容并更改检查(这意味着重新测试整个事情)。

如果您使用X声明设计该功能CanPerformX,则会引入一个抽象层:您的代码将不关心用户的角色,而只会检查其自己的声明。如果您重新设计了声明与用户的关联方式,您的有效代码将不会改变(这最终意味着没有引入正式的回归)。

角色被设计为广泛的,而声明被设计为细粒度的。然而,当您在链接中阅读时,您可能会将某个角色视为“大角色”,或者将某个角色视为“小角色”。

我发布了我的代码的一小段摘录,该代码支持自定义角色但固定声明。定义权利要求

    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";
    }
Run Code Online (Sandbox Code Playgroud)

定义政策

    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")));
        }
    }
Run Code Online (Sandbox Code Playgroud)

将政策注册在Startup.RegisterServices

    Policies.ConfigurePolicies(services);
Run Code Online (Sandbox Code Playgroud)

在对用户进行身份验证时,根据您的逻辑决定需要添加哪些声明(省略一些部分以重点关注概念)

    [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 ...
    }
Run Code Online (Sandbox Code Playgroud)

最后,您可以使用策略来保护您的代码:

    [Authorize(Policy = Policies.RequireAdministrator)]
    [HttpPost("execute")]
    public async Task<IActionResult> ExecuteOperation([FromBody] CommandModel model)
    {
        ...
    }
Run Code Online (Sandbox Code Playgroud)

请注意,在这种方法中,我将某些声明硬编码给管理员,因为我想防止管理员删除某些声明。然而,这不是强制性的。