密码强度校验必须在模型层(set_password()前)和表单层双端执行,统一由validate_password()触发,调用所有注册的passwordvalidator子类验证器,确保api、管理后台等各入口均生效。

密码强度校验应该放在哪一层?
Django 的密码校验逻辑默认由 django.contrib.auth.password_validation 提供,但自定义规则不能只靠前端或表单层拦截——用户可能绕过表单直接调用 API 或通过管理后台修改密码。必须在模型层(set_password() 调用前)和表单层(clean_password2())双端校验,且优先在 validate_password() 中统一触发。
- 校验入口点是
validate_password()函数,它会依次调用所有已注册的密码验证器 - 自定义验证器必须继承
PasswordValidator并实现validate()和get_help_text() - 不要重写
User.set_password(),否则会跳过内置校验链
如何编写一个带正则和最小长度的自定义验证器?
最常被忽略的是:Django 验证器不自动捕获异常类型,必须显式抛出 ValidationError,且错误消息需为字符串(不是列表),否则会导致 500 错误。
from django.core.exceptions import ValidationError
from django.contrib.auth.password_validation import PasswordValidator
<p>class StrongPasswordValidator(PasswordValidator):
def <strong>init</strong>(self, min_length=12, require_digit=True, require_upper=True):
self.min_length = min_length
self.require_digit = require_digit
self.require_upper = require_upper</p><pre class="brush:python;toolbar:false;">def validate(self, password, user=None):
if len(password)
- 注意
validate()的第二个参数user可能为None(如注册时),不要假设它一定存在 -
get_help_text()返回纯字符串,用于表单渲染提示,不能返回mark_safe或 HTML
如何启用并覆盖默认验证器?
Django 默认启用四个验证器(MinimumLengthValidator、CommonPasswordValidator 等),它们在 AUTH_PASSWORD_VALIDATORS 设置中注册。添加自定义验证器时,不要删除默认项,除非你明确知道后果(比如禁用 CommonPasswordValidator 会让 "password123" 通过)。
在 settings.py 中配置:
AUTH_PASSWORD_VALIDATORS = [
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
{'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 'OPTIONS': {'min_length': 8}},
{'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator'},
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
{'NAME': 'myapp.validators.StrongPasswordValidator', 'OPTIONS': {'min_length': 12}},
]
- 验证器按顺序执行,任一失败即中断,不会继续后续校验
-
OPTIONS中传参必须与验证器<strong>init</strong>参数名完全一致,拼错(如写成min_len)会导致参数未生效却无报错 - 如果你用的是 Django CommonPasswordValidator 依赖内置的 20000 条弱密码列表;升级后该列表已更新,但旧版本缓存可能未刷新
为什么用户改密码时校验不生效?
常见现象:在 Admin 后台或 SetPasswordForm 中修改密码,自定义规则没触发。根本原因是这些表单默认使用 AdminPasswordChangeForm 或 SetPasswordForm,它们内部调用 validate_password() 时未传入 user 实例,导致部分依赖 user 属性的校验逻辑被跳过。
- 解决办法:在自定义表单的
clean_new_password2()中显式调用validate_password()并传入self.user - 若表单无
self.user(如注册表单),则确保你的验证器不依赖该参数,或提前判空处理 - 另一个坑:Django REST Framework 的
SetPasswordSerializer默认不集成密码验证,需手动在save()前加validate_password()
真正麻烦的不是写验证器,而是验证器在不同上下文(注册 / 修改 / Admin / DRF)中被调用的方式不一致,每个入口都要单独确认是否走通了校验链。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











