ASP.NET 6脚手架Register页面未启用配置的密码策略及验证器问题
问题背景
我在站点Startup中配置了自定义密码策略:
builder.Services.Configure<IdentityOptions>(options => { options.Password.RequireDigit = true; options.Password.RequireLowercase = true; options.Password.RequireUppercase = true; options.Password.RequireNonAlphanumeric = true; options.Password.RequiredLength = 12; options.Password.RequiredUniqueChars = 1; });
同时还添加了自定义密码验证器,但不管是内置还是自定义验证器都没被触发。排查后发现问题出在使用了脚手架生成的Register.cshtml页面——无脚手架的测试站点里,密码策略能正常生效。
我目前用临时方法解决:在脚手架生成的RegisterModel的OnPostAsync方法中、ModelState.IsValid检查前加这段代码:
foreach(var pval in _userManager.PasswordValidators) { var result = await pval.ValidateAsync(_userManager, tuser, Input.Password); if (!result.Succeeded) result.Errors.ToList().ForEach(x => ModelState.AddModelError("", x.Description)); }
想知道正确的实现方式。
正确实现方式
方法1:调用UserManager内置的密码验证方法
不用手动遍历所有验证器,直接调用_userManager.ValidatePasswordAsync就行,这个方法会自动触发所有注册的验证器(内置+自定义):
var passwordValidationResult = await _userManager.ValidatePasswordAsync(tuser, Input.Password); if (!passwordValidationResult.Succeeded) { foreach (var error in passwordValidationResult.Errors) { ModelState.AddModelError(string.Empty, error.Description); } }
把这段代码替换你手动遍历的代码,放在ModelState.IsValid检查之前即可。
方法2:给InputModel的Password属性加验证特性
脚手架生成的RegisterModel里,默认InputModel的Password属性没关联Identity的密码验证规则。可以手动添加[Password]特性,让模型验证阶段自动触发密码策略检查:
public class InputModel { // 其他属性... [Required] [DataType(DataType.Password)] [Password] // 添加该特性 [Display(Name = "Password")] public string Password { get; set; } // 确认密码属性... }
注意:[Password]特性只会根据Startup里配置的IdentityOptions.Password规则验证,不会触发自定义密码验证器。如果有自定义验证器,还是要结合方法1的ValidatePasswordAsync调用。
方法3:重新生成脚手架时确保包含验证逻辑
如果重新生成注册页面脚手架,生成的代码会自动包含正确的密码验证逻辑。用dotnet aspnet-codegenerator identity命令时选对选项,生成的RegisterModel会自动调用_userManager.CreateAsync,这个方法内部会自动执行所有密码验证器。
问题根源
脚手架生成的RegisterModel默认直接调用_userManager.CreateAsync(user, Input.Password)创建用户,但如果调用前没手动验证密码,且Password属性没加[Password]特性,模型验证阶段就会跳过密码策略检查;而CreateAsync失败后错误信息可能没被正确添加到ModelState,看起来就像验证器没被调用。
内容的提问来源于stack exchange,提问作者Kevin O'Donovan

