在身份核心中运行自定义用户验证程序之前创建用户

User getting created before custom user validator runs in Identity core

我在 .net core 3.1 web api 中使用 identity core 进行用户管理。现在,我想检查用户的电子邮件,如果它符合要求,那么他才会被创建。下面的代码说明了我想要实现的目标

我有一个自定义用户验证器,如下所示:

 public class CustomEmailValidator<TUser> : IUserValidator<TUser>
   where TUser : User
{

    public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
                                              TUser user)
    {
        User userFromEmail = null;

        if(!string.IsNullOrEmpty(user.Email))
            userFromEmail = manager.FindByEmailAsync(user.Email).Result;

        if (userFromEmail == null)
            return Task.FromResult(IdentityResult.Success);

        return Task.FromResult(
        IdentityResult.Failed(new IdentityError
        {
            Code = "Err",
            Description = "You are already registered with us."
        }));
    }
}

我在我的启动中添加验证器如下:

services.AddDbContext<DataContext>(x => x.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));

        IdentityBuilder builder = services.AddIdentityCore<User>(opt =>
        {
            
            opt.User.RequireUniqueEmail = false;
            opt.User.AllowedUserNameCharacters = "abcdefghijklmnopqrstuvwxyz0123456789._-";
            opt.Password.RequireDigit = true;
            opt.Password.RequiredLength = 6;
            opt.Password.RequireNonAlphanumeric = true;
            opt.Password.RequireUppercase = true;
            opt.Password.RequireLowercase = true;
        })
        .AddUserValidator<CustomEmailValidator<User>>();
        builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
        builder.AddEntityFrameworkStores<DataContext>();
        builder.AddRoleValidator<RoleValidator<Role>>();
        builder.AddRoleManager<RoleManager<Role>>();
        builder.AddSignInManager<SignInManager<User>>();

可以看出,我也想使用默认的用户验证和我的自定义验证。问题是用户在默认验证后立即创建,并且电子邮件总是在我的自定义验证中存在。我真的不想覆盖我的默认验证。

创建用户如下:

    [HttpPost("Register")]
    public async Task<IActionResult> Register(UserForRegisterDto userForRegister)
    {
        var userToCreate = _mapper.Map<User>(userForRegister);

        var result = await _userManager.CreateAsync(userToCreate, userForRegister.Password);

        if (result.Succeeded)
        {
            var roleresult = await _userManager.AddToRoleAsync(userToCreate, "Member");
            return Ok(roleresult);
        }
        return BadRequest(result.Errors);
    }

注意这不是我的实际用例。我知道我可以通过使 opt.User.RequireUniqueEmail = true 在我的默认验证中检查唯一的电子邮件。这只是为了进一步发展明确一个概念。

Update 进一步调试后,我看到自定义验证方法被调用了两次。一次在用户创建之前,一次因为某种原因在创建之后。我插入一个新的唯一电子邮件,自定义验证成功通过,在创建用户后,再次调用自定义验证,发现电子邮件已经注册并抛出错误消息。这很奇怪

发现 AddToRoleAsync 再次调用自定义验证器并发现 table 中存在的用户。必须包括检查在 table 中找到的具有相同电子邮件的用户是否与正在更新的用户相同。

代码如下:

 public class CustomEmailValidator<TUser> : IUserValidator<TUser>
 where TUser : User
{

  public Task<IdentityResult> ValidateAsync(UserManager<TUser> manager,
                                          TUser user)
  {
    User userFromEmail = null;

    if(!string.IsNullOrEmpty(user.Email))
        userFromEmail = manager.FindByEmailAsync(user.Email).Result;

    if (userFromEmail == null)
        return Task.FromResult(IdentityResult.Success);
    else {
            if(string.Equals(userFromEmail.Id, user.Id))
            {
                return Task.FromResult(IdentityResult.Success);
            }
    }

    return Task.FromResult(
    IdentityResult.Failed(new IdentityError
    {
        Code = "Err",
        Description = "You are already registered with us."
    }));
  }
}

这应该可以帮助很多人