Pac*_*der 6 permissions oauth-2.0 jwt asp.net-identity .net-core
我正在使用的应用程序是SPA,在与使用.NETCore和ASP.NET Identity的后端API通信时,我们正在使用JWT Bearer身份验证和OpenIdConnect / OAuth2。我们的API端点使用基于自定义策略的身份验证来保护,如下所示:
我们决定使用现成的AspNetRoleClaims表将用户的声明存储为权限。每个用户都被分配了1个主要角色,尽管潜在的角色是多个角色。每个角色将有许多声明-存储在AspNetRoleClaims表中。
角色声明如下所示:
ClaimType:权限
ClaimValue(s):
MyModule1.Create
MyModule1.Read
MyModule1。编辑
MyModule1.Delete
MyModule1.SomeOtherPermission
MyModule2.Read
MyModule3.Read
MyModule3。编辑
等等
用户拥有的权限或角色越多,access_token就会越大,从而增加HTTP标头的大小。还有ASP.NET身份授权cookie-随着越来越多的角色声称它被拆分为多个cookie。
我尝试添加很多角色声明,最终请求失败,因为标头变得太大。
我正在寻找有关使用角色声明进行承载身份验证的“最佳实践”的建议。Microsoft为您提供了适用于我的方案的开箱即用的AspNetRoleClaims,据我所知,将这些角色声明存储在access_token中的优点是我们不必在受自定义策略保护的每个API终结点上访问数据库。
从我的角度来看,我可以尝试使声明值变小,并且在用户具有多个角色且可能共享重复的常见角色声明的情况下,我可以尝试在将这些声明写入Cookie和删除重复项。
但是,由于该应用程序仍在开发中,因此可以预见会有越来越多的角色声明被添加,并且由于Cookie和access_token的存在,HTTP标头始终可能变得太大。不知道这是否是最好的方法。
我看到的唯一替代方法是,每次我们访问受保护的API时都访问数据库。我可以在每个自定义声明策略要求处理程序中注入DbContext,并在每个请求上与AspNetRoleClaims表进行对话。
我还没有看到太多有关人们如何使用ASP.NET Identity和.NET Core API实现更细粒度的权限方案的示例。我认为这必须是一个相当普遍的要求。
无论如何,只要针对这种情况寻找有关最佳实践建议的反馈和建议。
****更新-请参阅下面的答案****
我从未找到关于如何完成此操作的推荐“最佳实践”,但是由于有一些有用的博客文章,我能够为我从事的项目设计一个不错的解决方案。我决定从id令牌和Identity cookie中排除身份声明,并进行每个请求检查用户权限(角色声明)服务器端的工作。
我最终使用了上面描述的体系结构,使用了内置的AspNetRoleClaims表,并使用给定角色的权限填充该表。
例如:
ClaimType:权限
ClaimValue(s):
MyModule1.Create
MyModule1.Read
MyModule1。编辑
MyModule1.Delete
我使用基于自定义策略的身份验证,如上面链接中的Microsoft文章所述。然后,我使用基于角色的策略锁定每个API端点。
我也有一个枚举类,其中所有权限都存储为枚举。这个枚举使我无需使用魔术字符串即可引用代码中的权限。
public enum Permission
{
[Description("MyModule1.Create")]
MyModule1Create,
[Description("MyModule1.Read")]
MyModule1Read,
[Description("MyModule1.Update")]
MyModule1Update,
[Description("MyModule1.Delete")]
MyModule1Delete
}
Run Code Online (Sandbox Code Playgroud)
我像这样在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)));
}
Run Code Online (Sandbox Code Playgroud)
因此,存在一个匹配的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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
权限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();
}
Run Code Online (Sandbox Code Playgroud)
我的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;
}
}
Run Code Online (Sandbox Code Playgroud)
我为用户建立了一系列不同的角色声明-我使用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;
}
}
Run Code Online (Sandbox Code Playgroud)
控制器通过基于角色的自定义策略锁定:
[HttpGet("{id}")]
[Authorize(Policy = "MyModule1Read", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult Get(int id){
Run Code Online (Sandbox Code Playgroud)
现在这里是重要的部分-您需要重写UserClaimsPrincipalFactory,以防止将角色声明填充到Identity cookie中。这解决了Cookie和标头太大的问题。感谢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);
}
}
Run Code Online (Sandbox Code Playgroud)
在Startup.cs中注册此类
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// override UserClaimsPrincipalFactory (to remove role claims from cookie )
services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
Run Code Online (Sandbox Code Playgroud)
以下是Ben Foster有用的博客文章的链接:
该解决方案在我正在进行的项目中效果很好-希望它可以帮助其他人。
| 归档时间: |
|
| 查看次数: |
1953 次 |
| 最近记录: |