Asp.Net身份验证和多租户中的重复角色名称

8

我正在使用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; }
}

这是因为每个租户都有各自的角色集,比如“管理员”、“员工”等。

但问题在于,当我添加一个新角色时,如果“租户A”有“管理员”角色,当我把“管理员”角色添加到“租户B”时,我会得到一个IdentityResult错误,因为“管理员”名称已被占用…很明显,因为AspNetRoles表中的“Name”字段是唯一的…

IdentityResult roleResult = await RoleManager.CreateAsync(
  new ApplicationRole
  {
    Name = "Admin",
    TenantId = GetTenantId()
  });

那么我应该如何定制ASP.Net Identity,使得“AspNetRoles”中的“Name”字段可以与“TenantId”一起成为唯一值而不是单独的?我找到了有关扩展IdentityRole(就像我添加字段一样)的信息,但没有关于更改或替换它的信息...

3个回答

8

只需在ApplicationDbContextOnModelCreating方法中修改数据库架构,如下所示:

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 类。因为默认的角色验证器会使重复的角色名称无效。
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();

Sam,当我尝试你的解决方案时,add-migration命令会引发以下错误: 在表'IdentityRole'的列“Name”上指定了冲突的配置设置:索引属性“IsUnique”='False'与索引属性“IsUnique”='True'相冲突。 - Click Ok
但之后它会抛出异常:一个或多个实体的验证失败。请参阅“EntityValidationErrors”属性获取更多详细信息。 - Partha Ranjan Nayak
@ClickOk,你是怎么解决这个问题的?你说它会抛出错误,但你标记了这个解决方案为已接受,它真的有效吗? - ibubi
@ibubi,抱歉,我记不得了。这个回答是三年前针对一个旧项目的。但是我的留言后被编辑过,所以可能这次编辑解决了问题。 - Click Ok

1

您可以在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>()

在运行代码之前,不要忘记将更改迁移到数据库中。


1
这是一个完整的答案,所有部分都得到了清晰的解释。谢谢。 - maulik13
谢谢,但是 builder.HasIndex(x => x.NormalizedName).IsUnique(false); 在迁移中没有改变任何东西,你知道为什么吗? - Tariq Hajeer
你能解释一下为什么这个对我没有用吗? 这是我的问题 https://dev59.com/XKolpogBymzxlkE8H4Dc - Tariq Hajeer
当添加您的验证器时,请不要忘记移除默认的 RoleValidator,因为默认情况下验证器是累积的(而不是替换)(https://github.com/dotnet/aspnetcore/issues/46066)。在注册您的验证器后,添加以下内容:`var defaultRoleValidator = services.FirstOrDefault(descriptor => descriptor.ImplementationType == typeof(RoleValidator<ApplicationRole>)); if (defaultRoleValidator != null) { services.Remove(defaultRoleValidator); }` - Rafael Neto
如果你没有使用_NormalizedName_索引(就像我的情况一样),那么可以避免使用_ApplicationRoleConfiguration_类。 - Rafael Neto
显示剩余2条评论

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接