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

问题描述 投票:0回答:4

我正在使用 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 表上的“名称”字段是唯一的...

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

但是我如何自定义 ASP.Net Identity,以便“AspNetRoles”中的“Name”字段可以与“TenantId”唯一,而不是单独存在?我找到了有关扩展 IdentityRole 的信息(就像我添加字段一样),但没有找到有关更改或替换它的信息...

asp.net-mvc multi-tenant asp.net-identity-2
4个回答
8
投票

只需在

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

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();

0
投票

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

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


0
投票

IdentityDbContext.ValidateEntity()

 中还有另一个
检查角色名称的唯一性,也必须覆盖它。


0
投票

来自 Cyber Progs 答案的 TenantRoleValidator 类有一个有效的实现:

using Microsoft.AspNetCore.Identity;

namespace IOTAPI.Infrastructure.Identity;
public class TenantRoleValidator : RoleValidator<ApplicationRole>
{
    private IdentityErrorDescriber Describer { get; set; }

    public TenantRoleValidator(IdentityErrorDescriber? errors = null) : base(errors)
    {
        Describer = errors ?? new IdentityErrorDescriber();
    }

    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 roleExists = manager.Roles.Any(r =>
                r.TenantId.Equals(role.TenantId) &&
                r.NormalizedName.Equals(manager.NormalizeKey(roleName)));

            if (roleExists)
            {
                errors.Add(Describer.DuplicateRoleName(roleName));
            }
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.