Asp.Net 身份和多租户中的重复角色名称
Duplicate Role Names on Asp.Net Identity and Multi-tenancy
我正在使用 ASP.Net MVC 和 Identity 2.0 开发多租户 Web 应用程序。我已经像这样扩展了 IdentityRole:
public class ApplicationRole : IdentityRole
{
public ApplicationRole() : base() { }
public ApplicationRole(string name) : base(name) { }
public string TenantId { get; set; }
}
这是因为每个租户都有自己的角色集,例如 "Admin"、"Staff" 等
但问题是,当我添加一个新角色时,如果 "Tenant A" 有 "Admin" 角色,当我向 "Tenant B" 添加 "Admin" 角色时,我得到一个 IdentityResult 错误,因为 "Admin" name is taken... 这有点明显,因为 AspNetRoles table 上的 "Name" 字段是唯一的...
IdentityResult roleResult = await RoleManager.CreateAsync(
new ApplicationRole
{
Name = "Admin",
TenantId = GetTenantId()
});
但是我如何自定义 ASP.Net 身份,以便 "AspNetRoles" 中的 "Name" 字段可以与 "TenantId" 唯一,而不是单独的?我找到了有关扩展 IdentityRole 的信息(就像我添加字段一样),但没有关于更改或替换它的信息...
只需在 ApplicationDbContext
的 OnModelCreating
方法上更改数据库的架构,如下所示:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var role = modelBuilder.Entity<IdentityRole>()
.ToTable("AspNetRoles");
role.Property(r => r.Name)
.IsRequired()
.HasMaxLength(256)
.HasColumnAnnotation("Index", new IndexAnnotation(
new IndexAttribute("RoleNameIndex")
{ IsUnique = false }));
}
但您还必须自定义 RoleValidator
class。因为默认角色验证器会使重复的角色名称无效。
public class MyRoleValidator:RoleValidator<ApplicationRole>
{
public override async Task<IdentityResult> ValidateAsync(ApplicationRole item)
{
// implement your validation logic here
return IdentityResult.Success;
}
}
现在每次创建角色管理器时都必须设置角色验证器。
roleManager.RoleValidator=new MyRoleValidator();
你可以在RoleName和TenantId上添加一个复合唯一约束,但是首先必须去掉角色名的唯一约束这里是代码:
public class ApplicationRoleConfiguration : IEntityTypeConfiguration<ApplicationRole>
{
public void Configure(EntityTypeBuilder<ApplicationRole> builder)
{
//remove the current idenx
builder.HasIndex(x => x.NormalizedName).IsUnique(false);
// add composite constraint
builder.HasIndex(x => new { x.NormalizedName, x.TenantId }).IsUnique();
}
}
然后必须覆盖角色验证器以检查角色名称和 TenantId 的唯一性:
public class TenantRoleValidator : RoleValidator<ApplicationRole>
{
private IdentityErrorDescriber Describer { get; set; }
public TenantRoleValidator() : base()
{
}
public override async Task<IdentityResult> ValidateAsync(RoleManager<ApplicationRole> manager, ApplicationRole role)
{
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
var errors = new List<IdentityError>();
await ValidateRoleName(manager, role, errors);
if (errors.Count > 0)
{
return IdentityResult.Failed(errors.ToArray());
}
return IdentityResult.Success;
}
private async Task ValidateRoleName(RoleManager<ApplicationRole> manager, ApplicationRole role,
ICollection<IdentityError> errors)
{
var roleName = await manager.GetRoleNameAsync(role);
if (string.IsNullOrWhiteSpace(roleName))
{
errors.Add(Describer.InvalidRoleName(roleName));
}
else
{
var owner = await manager.FindByNameAsync(roleName);
if (owner != null
&& owner.TenantId == role.TenantId
&& !string.Equals(await manager.GetRoleIdAsync(owner), await manager.GetRoleIdAsync(role)))
{
errors.Add(Describer.DuplicateRoleName(roleName));
}
}
}
}
最后,注册新的角色验证器:
services
.AddIdentityCore<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddRoleValidator<TenantRoleValidator>()
不要忘记在 运行 代码
之前将更改迁移到数据库
我正在使用 ASP.Net MVC 和 Identity 2.0 开发多租户 Web 应用程序。我已经像这样扩展了 IdentityRole:
public class ApplicationRole : IdentityRole
{
public ApplicationRole() : base() { }
public ApplicationRole(string name) : base(name) { }
public string TenantId { get; set; }
}
这是因为每个租户都有自己的角色集,例如 "Admin"、"Staff" 等
但问题是,当我添加一个新角色时,如果 "Tenant A" 有 "Admin" 角色,当我向 "Tenant B" 添加 "Admin" 角色时,我得到一个 IdentityResult 错误,因为 "Admin" name is taken... 这有点明显,因为 AspNetRoles table 上的 "Name" 字段是唯一的...
IdentityResult roleResult = await RoleManager.CreateAsync(
new ApplicationRole
{
Name = "Admin",
TenantId = GetTenantId()
});
但是我如何自定义 ASP.Net 身份,以便 "AspNetRoles" 中的 "Name" 字段可以与 "TenantId" 唯一,而不是单独的?我找到了有关扩展 IdentityRole 的信息(就像我添加字段一样),但没有关于更改或替换它的信息...
只需在 ApplicationDbContext
的 OnModelCreating
方法上更改数据库的架构,如下所示:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
var role = modelBuilder.Entity<IdentityRole>()
.ToTable("AspNetRoles");
role.Property(r => r.Name)
.IsRequired()
.HasMaxLength(256)
.HasColumnAnnotation("Index", new IndexAnnotation(
new IndexAttribute("RoleNameIndex")
{ IsUnique = false }));
}
但您还必须自定义 RoleValidator
class。因为默认角色验证器会使重复的角色名称无效。
public class MyRoleValidator:RoleValidator<ApplicationRole>
{
public override async Task<IdentityResult> ValidateAsync(ApplicationRole item)
{
// implement your validation logic here
return IdentityResult.Success;
}
}
现在每次创建角色管理器时都必须设置角色验证器。
roleManager.RoleValidator=new MyRoleValidator();
你可以在RoleName和TenantId上添加一个复合唯一约束,但是首先必须去掉角色名的唯一约束这里是代码:
public class ApplicationRoleConfiguration : IEntityTypeConfiguration<ApplicationRole>
{
public void Configure(EntityTypeBuilder<ApplicationRole> builder)
{
//remove the current idenx
builder.HasIndex(x => x.NormalizedName).IsUnique(false);
// add composite constraint
builder.HasIndex(x => new { x.NormalizedName, x.TenantId }).IsUnique();
}
}
然后必须覆盖角色验证器以检查角色名称和 TenantId 的唯一性:
public class TenantRoleValidator : RoleValidator<ApplicationRole>
{
private IdentityErrorDescriber Describer { get; set; }
public TenantRoleValidator() : base()
{
}
public override async Task<IdentityResult> ValidateAsync(RoleManager<ApplicationRole> manager, ApplicationRole role)
{
if (manager == null)
{
throw new ArgumentNullException(nameof(manager));
}
if (role == null)
{
throw new ArgumentNullException(nameof(role));
}
var errors = new List<IdentityError>();
await ValidateRoleName(manager, role, errors);
if (errors.Count > 0)
{
return IdentityResult.Failed(errors.ToArray());
}
return IdentityResult.Success;
}
private async Task ValidateRoleName(RoleManager<ApplicationRole> manager, ApplicationRole role,
ICollection<IdentityError> errors)
{
var roleName = await manager.GetRoleNameAsync(role);
if (string.IsNullOrWhiteSpace(roleName))
{
errors.Add(Describer.InvalidRoleName(roleName));
}
else
{
var owner = await manager.FindByNameAsync(roleName);
if (owner != null
&& owner.TenantId == role.TenantId
&& !string.Equals(await manager.GetRoleIdAsync(owner), await manager.GetRoleIdAsync(role)))
{
errors.Add(Describer.DuplicateRoleName(roleName));
}
}
}
}
最后,注册新的角色验证器:
services
.AddIdentityCore<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddRoleValidator<TenantRoleValidator>()
不要忘记在 运行 代码
之前将更改迁移到数据库