如何覆盖ASP.NET Core Identity的密码策略

75
默认情况下,ASP.NET Core Identity的密码策略要求至少一个特殊字符、一个大写字母、一个数字等。如何更改这些限制?文档(https://docs.asp.net/en/latest/security/authentication/identity.html)中没有相关内容。我试图重写Identity的User Manager,但我找不到哪个方法管理密码策略。
public class ApplicationUserManager : UserManager<ApplicationUser>
{
    public ApplicationUserManager(
        DbContextOptions<SecurityDbContext> options,
        IServiceProvider services,
        IHttpContextAccessor contextAccessor,
        ILogger<UserManager<ApplicationUser>> logger)
        : base(
              new UserStore<ApplicationUser>(new SecurityDbContext(contextAccessor)),
              new CustomOptions(),
              new PasswordHasher<ApplicationUser>(),
              new UserValidator<ApplicationUser>[] { new UserValidator<ApplicationUser>() },
              new PasswordValidator[] { new PasswordValidator() },
              new UpperInvariantLookupNormalizer(),
              new IdentityErrorDescriber(),
              services,
              logger
            // , contextAccessor
              )
    {
    }

    public class PasswordValidator : IPasswordValidator<ApplicationUser>
    {
        public Task<IdentityResult> ValidateAsync(UserManager<ApplicationUser> manager, ApplicationUser user, string password)
        {
            return Task.Run(() =>
            {
                if (password.Length >= 4) return IdentityResult.Success;
                else { return IdentityResult.Failed(new IdentityError { Code = "SHORTPASSWORD", Description = "Password too short" }); }
            });
        }
    }

    public class CustomOptions : IOptions<IdentityOptions>
    {
        public IdentityOptions Value { get; private set; }
        public CustomOptions()
        {
            Value = new IdentityOptions
            {
                ClaimsIdentity = new ClaimsIdentityOptions(),
                Cookies = new IdentityCookieOptions(),
                Lockout = new LockoutOptions(),
                Password = null,
                User = new UserOptions(),
                SignIn = new SignInOptions(),
                Tokens = new TokenOptions()
            };
        }
    }
}

我在启动类中添加了此用户管理依赖项:

services.AddScoped<ApplicationUserManager>();

但是当我在控制器中使用ApplicationUserManager时,出现错误:

InvalidOperationException:尝试激活'ApplicationUserManager'时无法解析类型为'Microsoft.EntityFrameworkCore.DbContextOptions`1[SecurityDbContext]'的服务。

编辑:当我使用ASP.NET Core Identity的默认类时,用户管理工作正常,因此这不是数据库问题或类似问题。


2
有趣的事实:MS强制执行的默认密码规则对于由Chrome密码管理器生成的密码来说太严格了。 - Michiel Cornille
6个回答

182

最终,这非常简单...

无需覆盖任何类,您只需要在启动类中配置身份设置,像这样:

services.Configure<IdentityOptions>(options =>
{
    options.Password.RequireDigit = false;
    options.Password.RequiredLength = 5;
    options.Password.RequireLowercase = true;
    options.Password.RequireNonLetterOrDigit = true;
    options.Password.RequireUppercase = false;
});

或者在添加时配置身份:

services.AddIdentity<ApplicationUser, IdentityRole>(options=> {
                options.Password.RequireDigit = false;
                options.Password.RequiredLength = 4;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase = false;
                options.Password.RequireLowercase = false;
            })
                .AddEntityFrameworkStores<SecurityDbContext>()
                .AddDefaultTokenProviders();

ASP.NET Core 绝对是一项不错的技术...


4
已添加解决方案到官方文档 https://docs.asp.net/en/latest/security/authentication/identity.html - AdrienTorris
14
根据你提供的解决方案,在ASP .Net Core中,选项options.Password.RequireNonAlphanumeric = false;已被弃用,并分成了两个“子选项”:options.Password.RequireDigit = false;options.Password.RequireNonAlphanumeric = false;。请注意,翻译后不包括任何解释或其他内容。 - Xavier Peña
有趣的是,我想允许调试时密码长度为1,但是options.Password.RequiredLength = 1;并没有起作用(尝试了上述两种情况)。验证仍然需要6个字符。 其他选项可以正常工作。 - J W
问题是,当我在属性上使用[DataType(DataType.Password)]时,ModelState始终等于true,因为密码验证未被处理。你有什么想法为什么会这样? - Cedric Arnould
1
有没有任何解决方法可以绕过硬编码的最小长度为6个字符,以便允许4个字符? - kerzek
显示剩余2条评论

8

开发者最简单的方法是

services.AddDefaultIdentity<IdentityUser>(options =>
{
  options.SignIn.RequireConfirmedAccount = true;
  options.Password.RequireDigit = false;
  options.Password.RequireNonAlphanumeric = false;
  options.Password.RequireUppercase = false;
  options.Password.RequireLowercase = false;
})
  .AddEntityFrameworkStores<ApplicationDbContext>();

只有 Password.RequiredLength 无法以这种方式更改,它仍然等于6。


他们在新版本中有这个功能! - Sujit Senapati

7

额外要求:

如果您认为这个密码约束不够,请通过继承PasswordValidator类来定义您自己的条件。

示例实现:

public class CustomPasswordPolicy : 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
                {
                    Description = "Password cannot contain username"
                });
            }
            if (password.Contains("123"))
            {
                errors.Add(new IdentityError
                {
                    Description = "Password cannot contain 123 numeric sequence"
                });
            }
            return errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray());
        }
    }

我已经重写了我的类中的ValidateAsync方法,并在该方法内实现了自定义密码策略。

非常重要

  • ValidateAsync()方法中的第一行代码:

IdentityResult result = await base.ValidateAsync(manager, user, password);

根据Startup类中ConfigureServices方法中指定的密码规则验证密码(这是此帖子旧答案中显示的那个方法)。

  • 密码验证功能由Microsoft.AspNetCore.Identity命名空间中的IPasswordValidator接口定义。因此,我需要将我的“CustomPasswordPolicy”类注册为“AppUser”对象的密码验证器。
    services.AddTransient<IPasswordValidator<AppUser>, CustomPasswordPolicy>();
            services.AddDbContext<AppIdentityDbContext>(options => options.UseSqlServer(Configuration["ConnectionStrings:DefaultConnection"]));
            services.AddIdentity<AppUser, IdentityRole>(opts =>
            {
                opts.Password.RequiredLength = 8;
                opts.Password.RequireNonAlphanumeric = true;
                opts.Password.RequireLowercase = false;
                opts.Password.RequireUppercase = true;
                opts.Password.RequireDigit = true;
            }).AddEntityFrameworkStores<AppIdentityDbContext>().AddDefaultTokenProviders();

PasswordValidator.cs官方Github文档(为了更好地理解):这里


嗨,如果在注册时还没有关联用户,会发生什么? - Cedric Arnould

6
在startup.cs的ConfigureServices方法中添加以下代码行。
services.Configure<IdentityOptions>(Configuration.GetSection(nameof(IdentityOptions)));

如果您希望,可以使用不同的部分名称

然后在配置文件中添加设置。您可以在多个配置源中添加多个设置,它们将被合并。 例如,我将它放入我的appsettings.local.json文件中。这个文件被VCS忽略,因此我的本地设置永远不会像硬编码设置和使用#if debug之类的设置那样上线。

"IdentityOptions": {
"Password": {
  "RequiredLength": 6,
  "RequireDigit": false,
  "RequiredUniqueChars": 1,
  "RequireLowercase": false,
  "RequireNonAlphanumeric": false,
  "RequireUppercase": false
 }
}

同样适用于appsettings.{Environment}.json或任何其他配置源,因此您可以在开发服务器和生产服务器上具有不同的设置,而无需更改代码或使用不同的构建配置。

3
你可以在IdentityConfig.cs文件中修改这些规则。 这些规则是在HTML标签中定义的。
public static ApplicationUserManager Create(IdentityFactoryOptions<ApplicationUserManager> options, IOwinContext context)
{
    var manager = new ApplicationUserManager(new UserStore<ApplicationUser>(context.Get<ApplicationDbContext>()));
    // Configure validation logic for usernames
    manager.UserValidator = new UserValidator<ApplicationUser>(manager)
    {
        AllowOnlyAlphanumericUserNames = false,
        RequireUniqueEmail = true
    };

    // Configure validation logic for passwords
    manager.PasswordValidator = new PasswordValidator
    {
        RequiredLength = 5,
        RequireNonLetterOrDigit = false,
        RequireDigit = true,
        RequireLowercase = true,
        RequireUppercase = true,
    };
}

这不是针对AspNetCore,而是针对.NET框架! - Sujit Senapati

0
我提出这个解决方案:
    {
 IdentityResult result = await base.ValidateAsync(manager, user, password);
}

这是基于一个事实的,即这个验证函数将被运行2次。
第一次用于PasswordValidator,第二次在CustomPasswordPolicy中再次运行。


我已经将您的格式和措辞转换为更明显的答案,并附上了解释。如果这不是您想要的,请自行[编辑](随意撤销我的更改),并确保它符合[答案]。 - Yunnosch

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