gpt4 book ai didi

permissions - 将角色声明作为权限的推荐最佳实践

转载 作者:行者123 更新时间:2023-12-04 14:40:22 27 4
gpt4 key购买 nike

我正在开发的应用程序是一个 SPA,我们在与使用 .NETCore 和 ASP.NET Identity 的后端 API 通信时使用 JWT Bearer 身份验证和 OpenIdConnect/OAuth2。我们的 API 端点使用基于自定义策略的身份验证进行保护,如下所示:

Custom Policy Based Authentication

我们决定使用开箱即用的 AspNetRoleClaims 表来为我们的用户存储声明作为权限。尽管有可能拥有多个角色,但每个用户都被分配了 1 个主要角色。每个角色将有许多声明 - 存储在 AspNetRoleClaims 表中。

角色声明如下所示:

ClaimType:权限

claim 值:

MyModule1.创建

MyModule1.Read

MyModule1.Edit

MyModule1.Delete

MyModule1.SomeOtherPermission

MyModule2.Read

MyModule3.Read

MyModule3.Edit

等等

用户拥有的权限或角色声明越多,access_token 就越大,从而增加 HTTP header 大小。还有 ASP.NET 身份授权 cookie - 随着越来越多的角色声明,它被分成多个 cookie。

我已经尝试添加很多角色声明,最终请求失败,因为 header 太大。

在使用角色声明进行承载身份验证时,我正在寻找一些关于什么被认为是“最佳实践”的建议。 Microsoft 为您提供了适用于我的场景的开箱即用的 AspNetRoleClaims,据我了解,将这些角色声明存储在 access_token 中的优势在于,我们不必在使用自定义策略保护的每个 API 端点上访问数据库.

在我看来,我可以尝试使声明值更小,并且在用户有多个角色可能共享重复的公共(public)角色声明的情况下,我可以尝试在这些被写入 cookie 和删除重复项。

但是,由于该应用程序仍在开发中,我可以预见将添加越来越多的角色声明,并且始终存在 HTTP header 与 cookie 和 access_token 一起变得太大的可能性。不确定这是否是最好的方法。

我看到的唯一选择是每次我们访问 protected API 时都访问数据库。我可以在每个自定义声明策略要求处理程序中注入(inject)一个 DbContext,并在每个请求上与 AspNetRoleClaims 表对话。

我还没有看到太多关于人们如何使用 ASP.NET Identity 和 .NET Core API 完成更细粒度的权限方案的示例。我认为这一定是一个相当普遍的要求......

无论如何,只是在这种情况下寻找一些关于推荐的最佳实践的反馈和建议。

****更新 - 请参阅下面的答案****

最佳答案

我从来没有找到关于如何实现这一点的推荐“最佳实践”,但感谢一些有用的博客文章,我能够为我正在从事的项目构建一个很好的解决方案。我决定从 id token 和身份 cookie 中排除身份声明,并针对每个请求检查用户权限(角色声明)服务器端的工作。

我最终使用了上述架构,使用内置的 AspNetRoleClaims 表并使用给定角色的权限填充它。

例如:

ClaimType:权限

claim 值:

MyModule1.创建

MyModule1.Read

MyModule1.Edit

MyModule1.Delete

如上面链接中的 Microsoft 文章中所述,我使用基于自定义策略的身份验证。
然后我使用基于角色的策略锁定我的每个 API 端点。

我还有一个枚举类,它所有的权限都存储为枚举。这个枚举只是让我在代码中引用权限,而不必使用魔术字符串。

public enum Permission
{
[Description("MyModule1.Create")]
MyModule1Create,
[Description("MyModule1.Read")]
MyModule1Read,
[Description("MyModule1.Update")]
MyModule1Update,
[Description("MyModule1.Delete")]
MyModule1Delete
}

我在 Startup.cs 中注册权限,如下所示:

services.AddAuthorization(options =>
{
options.AddPolicy("MyModule1Create",
p => p.Requirements.Add(new PermissionRequirement(Permission.MyModule1Create)));
options.AddPolicy("MyModule1Read",
p => p.Requirements.Add(new PermissionRequirement(Permission.MyModule1Read)));
options.AddPolicy("MyModule1Update",
p => p.Requirements.Add(new PermissionRequirement(Permission.MyModule1Update)));
options.AddPolicy("MyModule1Delete",
p => p.Requirements.Add(new PermissionRequirement(Permission.MyModule1Delete)));
}

所以有一个匹配的 Permission 和 PermissionRequirement ,如下所示:

public class PermissionRequirement : IAuthorizationRequirement
{
public PermissionRequirement(Permission permission)
{
Permission = permission;
}

public Permission Permission { get; set; }
}

public class PermissionRequirementHandler : AuthorizationHandler<PermissionRequirement>,
IAuthorizationRequirement

{
private readonly UserManager<User> _userManager;
private readonly IPermissionsBuilder _permissionsBuilder;

public PermissionRequirementHandler(UserManager<User> userManager,
IPermissionsBuilder permissionsBuilder)
{
_userManager = userManager;
_permissionsBuilder = permissionsBuilder;
}

protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
if (context.User == null)
{
return;
}

var user = await _userManager.GetUserAsync(context.User);
if (user == null)
{
return;
}

var roleClaims = await _permissionsBuilder.BuildRoleClaims(user);

if (roleClaims.FirstOrDefault(c => c.Value == requirement.Permission.GetEnumDescription()) != null)
{
context.Succeed(requirement);
}

}
}

权限 GetEnumDescription 上的扩展方法仅采用我在代码中为每个权限提供的枚举,并将其转换为与存储在数据库中的字符串名称相同的字符串名称。

public static string GetEnumDescription(this Enum value)
{
FieldInfo fi = value.GetType().GetField(value.ToString());

DescriptionAttribute[] attributes =
(DescriptionAttribute[])fi.GetCustomAttributes(
typeof(DescriptionAttribute),
false);

if (attributes != null &&
attributes.Length > 0)
return attributes[0].Description;
else
return value.ToString();
}

我的 PermissionHandler 有一个 PermissionsBuilder 对象。这是我编写的一个类,它将访问数据库并检查登录用户是否具有特定的角色声明。

public class PermissionsBuilder : IPermissionsBuilder
{
private readonly RoleManager<Role> _roleManager;

public PermissionsBuilder(UserManager<User> userManager, RoleManager<Role> roleManager)
{
UserManager = userManager;
_roleManager = roleManager;

}

public UserManager<User> UserManager { get; }

public async Task<List<Claim>> BuildRoleClaims(User user)
{
var roleClaims = new List<Claim>();
if (UserManager.SupportsUserRole)
{
var roles = await UserManager.GetRolesAsync(user);
foreach (var roleName in roles)
{
if (_roleManager.SupportsRoleClaims)
{
var role = await _roleManager.FindByNameAsync(roleName);
if (role != null)
{
var rc = await _roleManager.GetClaimsAsync(role);
roleClaims.AddRange(rc.ToList());
}
}
roleClaims = roleClaims.Distinct(new ClaimsComparer()).ToList();
}
}
return roleClaims;
}
}

我为用户建立了一个不同角色声明的列表——我使用 ClaimsComparer 类来帮助做到这一点。

public class ClaimsComparer : IEqualityComparer<Claim>
{
public bool Equals(Claim x, Claim y)
{
return x.Value == y.Value;
}
public int GetHashCode(Claim claim)
{
var claimValue = claim.Value?.GetHashCode() ?? 0;
return claimValue;
}
}

Controller 使用基于角色的自定义策略锁定:

[HttpGet("{id}")]
[Authorize(Policy = "MyModule1Read", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Get(int id){

现在这是重要的部分 - 您需要覆盖 UserClaimsPrincipalFactory 以防止角色声明被填充到身份 cookie 中。这样就解决了 cookie 和 header 太大的问题。感谢 Ben Foster 的有用帖子(见下面的链接)

这是我的自定义 AppClaimsPrincipalFactory:

public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<User, Role>
{
public AppClaimsPrincipalFactory(UserManager<User> userManager, RoleManager<Role> roleManager, IOptions<IdentityOptions> optionsAccessor)
: base(userManager, roleManager, optionsAccessor)
{
}
public override async Task<ClaimsPrincipal> CreateAsync(User user)
{
if (user == null)
{
throw new ArgumentNullException(nameof(user));
}
var userId = await UserManager.GetUserIdAsync(user);
var userName = await UserManager.GetUserNameAsync(user);
var id = new ClaimsIdentity("Identity.Application",
Options.ClaimsIdentity.UserNameClaimType,
Options.ClaimsIdentity.RoleClaimType);
id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName));
if (UserManager.SupportsUserSecurityStamp)
{
id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
await UserManager.GetSecurityStampAsync(user)));
}

// code removed that adds the role claims

if (UserManager.SupportsUserClaim)
{
id.AddClaims(await UserManager.GetClaimsAsync(user));
}

return new ClaimsPrincipal(id);
}
}

在 Startup.cs 中注册这个类

services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();

// override UserClaimsPrincipalFactory (to remove role claims from cookie )
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();

以下是 Ben Foster 有用的博客文章的链接:

AspNet Identity Role Claims

Customizing claims transformation in AspNet Core Identity

该解决方案对于我正在从事的项目效果很好 - 希望它可以帮助其他人。

关于permissions - 将角色声明作为权限的推荐最佳实践,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42562660/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com