Django 的 PASSWORD_VALIDATORS 是实施密码复杂度策略的唯一推荐入口,因其在 create_user()、form.save() 和 set_password() 三处自动触发,覆盖视图、Admin、API、命令行等全部路径,避免手动校验导致的规则遗漏。

直接上结论:Django 的 PASSWORD_VALIDATORS 配置是实施密码复杂度策略的唯一推荐入口,硬编码校验逻辑到视图或表单里等于绕过整个验证体系,极易漏掉模型层、管理后台、API 等关键路径。
为什么必须用 PASSWORD_VALIDATORS 而不是手动 check_password 或视图里写 if 判断
Django 的密码验证器是全局生效的钩子,它在三个关键位置自动触发:User.objects.create_user()、form.save()(含 UserCreationForm)、以及调用 user.set_password() 时。如果你只在修改密码视图里手写校验,那管理员在 Django Admin 后台重置密码、或 API 接口批量创建用户时,这些规则就完全失效。
常见错误现象:
- 用户在 Admin 后台设了
123456,没报错,但你的视图校验明明写了长度检查 - 第三方登录(如 OAuth)后首次设置密码,跳过了你写的视图逻辑,直接进数据库
-
manage.py changepassword命令行工具也不走你的视图
如何配置一个兼顾安全与可用性的密码验证器
在 settings.py 中启用并组合多个验证器,比单个“超级验证器”更清晰、更易调试:
立即学习“Python免费学习笔记(深入)”;
password_validators = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
'OPTIONS': {'max_similarity': 0.7}
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 10}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
'OPTIONS': {'password_list_path': '/path/to/custom-common-passwords.txt'}
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'
}
]关键说明:
-
UserAttributeSimilarityValidator默认检查用户名、邮箱、姓名是否出现在密码中;max_similarity: 0.7表示允许最多 70% 字符重合(避免误杀像alice2024!这类合理密码) -
MinimumLengthValidator设为 10 而非 8,是因为 NIST SP 800-63B 已明确放弃强制多样性,转而强调长度——10 位随机小写字母密码的熵(~48 bit)已高于 8 位混合密码(~42 bit) - 自定义
CommonPasswordValidator的password_list_path必须指向绝对路径,且文件每行一个明文弱密码(如password123),Django 不会自动下载或更新该列表
自定义验证器必须继承 PasswordValidator 并重写 validate()
不要直接抛 ValidationError,否则无法被 PASSWORD_VALIDATORS 机制识别。正确写法:
from django.contrib.auth.password_validation import PasswordValidator from django.core.exceptions import ValidationError <p>class NoSequentialDigitsValidator(PasswordValidator): def validate(self, password, user=None): for i in range(len(password) - 2): seq = password[i:i+3] if seq.isdigit() and int(seq[0]) + 1 == int(seq[1]) and int(seq[1]) + 1 == int(seq[2]): raise ValidationError( '密码不能包含连续数字(如 123、456)', code='password_has_sequential_digits' )</p>
注意点:
- 必须继承
PasswordValidator,不能只写普通函数 -
validate()方法签名固定为(self, password, user=None),user参数可能为None(如命令行创建用户),需兼容 - 错误
code值必须唯一,用于前端精准提示(比如前端根据password_has_sequential_digits显示特定文案) - 不要在验证器里调用
user.check_password()——那是认证阶段的事,验证器只管新密码本身
最容易被忽略的兼容性陷阱
验证器只对新设/修改密码生效,对已有弱密码用户不强制重设。这意味着上线后,数据库里仍存在大量旧密码。如果你需要渐进式加固:
- 先上线验证器,观察日志中
ValidationError的频率(可加logger.exception包裹) - 用
./manage.py changepassword username手动测试各路径是否触发 - 切勿在生产环境直接清空
PASSWORD_HASHERS或降级哈希算法——这会导致所有用户无法登录 - 如果用户反馈“密码改不了”,优先检查浏览器是否缓存了旧表单、或前端 JS 是否截断了密码字段(如
maxlength="16")
真正难的不是写验证逻辑,而是让所有密码入口——包括你没想到的那个——都经过同一套规则。漏掉一个,整套策略就形同虚设。


















