为什么密码被验证两次?

why password is validated twice?

我正在使用自定义验证配置 ASP.NET Core Identity 的密码验证,因此在 startup.cs:

public void ConfigureServices(IServiceCollection services)
{
   ...
   services.AddIdentity<AppUser, IdentityRole>( opts => {
       opts.Password.RequiredLength = 6;
   }).AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

    services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordValidator>();
   ...
}

我的客户密码验证器是

public class CustomPasswordValidator : PasswordValidator<AppUser>
{
    public override async Task<IdentityResult> ValidateAsync(UserManager<AppUser> manager, AppUser user, string password)
    {
        IdentityResult result = await base.ValidateAsync(manager, user, password);
        List<IdentityError> errors = result.Succeeded ? new List<IdentityError>() : result.Errors.ToList();
        if (password.ToLower().Contains(user.UserName.ToLower()))
        {
            errors.Add(new IdentityError
            {
                Code = "PasswordContainsUserName",
                Description = "Password cannot contain username"
            });
        }
       
        return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
    }

}

当我 运行 应用程序并输入长度 < 6 的无效密码时,会出现重复的验证输出:

Passwords must be at least 6 characters.

Passwords must be at least 6 characters.

我猜是因为我调用了baseValidateAsync()(其中包含startup.cs中的验证登录),但那不是我的CustomPasswordValidator 覆盖baseValidateAsync(),所以base的验证应该只调用一次?

我发现您需要在 service.AddIdentity

之前注册您的 CustomPasswordValidator
services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordValidator>();

services.AddIdentity<AppUser, IdentityRole>( opts => {
   opts.Password.RequiredLength = 6;
}).AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();
services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordValidator>();

此调用不会替换调用AddIdentity已添加的IPasswordValidator<AppUser>注册;它 添加另一个 。这意味着您最终会得到 两个 个密码验证器,它们都检查同一组内置规则。

通常,当向 DI 请求类型时,我们会要求一个单一的实现。这是一个示例构造函数:

public SomeClass(ISomeService someService) { }

如果 two 实现已为 ISomeService 注册,此构造函数将获得已注册 last 的实例].但是,我们仍然可以通过更新构造函数来请求 集合 of ISomeService 来获得 both 个实例。这是一个例子:

public SomeClass(IEnumerable<ISomeService> someServices) { }

在这种情况下,ISomeService 的两个已注册实现,someServices 包含 两个 实现的实例。这正是 what happens in UserManager,它旨在支持多个验证器。

查看 UserManager.ValidatePasswordAsyncsource 显示验证器是如何按顺序枚举和执行的:

foreach (var v in PasswordValidators)
{
    var result = await v.ValidateAsync(this, user, password);
    if (!result.Succeeded)
    {
        errors.AddRange(result.Errors);
    }
}

这意味着,CustomPasswordValidator 可以直接实现 IPasswordValidator<AppUser> 而不是扩展 PasswordValidator<AppUser>

public class CustomPasswordValidator : IPasswordValidator<AppUser>
{
    public async Task<IdentityResult> ValidateAsync(UserManager<AppUser> manager, AppUser user, string password)
    {
        // ...
    }
}

您的实现方法中的代码保持不变,除了调用 base.