Django 的注册流程相对复杂。下面选择在 admin 界面中新建用户的流程,来梳理下这其中的逻辑。
如果你用的是默认的 Django 模板,那么 Django 的 auth 模块是默认启用的;如果你没有用,参考 官方文档 打开 auth,并 打开 admin 功能。
执行过初始化时的 python manage.py migrate
后,会在数据库建立 auth_user
表用来存放用户信息,对应 django.contrib.auth.models.User
。将 Django server 运行起来,打开新建用户的页面,写入用户名密码:
这个页面是由 django.contrib.auth.admin.UserAdmin
通过 Django admin 提供的定制能力创建的。页面内容主要定义在:
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
add_form_template = 'admin/auth/user/add_form.html'
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password1', 'password2'),
}),
)
add_form = UserCreationForm
# ...
add_form_template
定义了页面模板add_fieldsets
定义了什么字段需要填写add_form
定义了提交的数据由哪个Form
来验证及处理
提交数据后,处理逻辑来到了 django.contrib.auth.forms.UserCreationForm
。这段逻辑比较重要:
from django.contrib.auth.models import User
class UserCreationForm(forms.ModelForm):
"""
A form that creates a user, with no privileges, from the given username and
password.
"""
error_messages = {
'password_mismatch': _('The two password fields didn’t match.'),
}
password1 = forms.CharField(
label=_("Password"),
strip=False,
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
help_text=password_validation.password_validators_help_text_html(),
)
password2 = forms.CharField(
label=_("Password confirmation"),
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
help_text=_("Enter the same password as before, for verification."),
)
class Meta:
model = User
fields = ("username",)
field_classes = {'username': UsernameField}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self._meta.model.USERNAME_FIELD in self.fields:
self.fields[self._meta.model.USERNAME_FIELD].widget.attrs['autofocus'] = True
def clean_password2(self):
password1 = self.cleaned_data.get("password1")
password2 = self.cleaned_data.get("password2")
if password1 and password2 and password1 != password2:
raise forms.ValidationError(
self.error_messages['password_mismatch'],
code='password_mismatch',
)
return password2
def _post_clean(self):
super()._post_clean()
# Validate the password after self.instance is updated with form data
# by super().
password = self.cleaned_data.get('password2')
if password:
try:
password_validation.validate_password(password, self.instance)
except forms.ValidationError as error:
self.add_error('password2', error)
def save(self, commit=True):
user = super().save(commit=False)
user.set_password(self.cleaned_data["password1"])
if commit:
user.save()
return user
Django form 对数据做校验的流程看 官方文档。这段代码的重点是:
clean_password2()
中校验了用户两次输入的密码是否一致_post_clean()
是个内部 hook,会在做完全部校验后执行。这里对密码做了额外校验(比如长度不能太短、不能跟用户名太相似、不能是常见密码)。放在_post_clean()
是因为 Django 在这里已经 prepopulate 了(即生成好了还未保存进数据库)一个 User 实例self.instance
,它包含了用户名等信息,可以给 password validator 做用户名相似性的校验- 最后
save()
将用户数据保存到数据库。它先用了commit=False
来生成一个django.contrib.auth.models.User
的实例,再调用set_password()
将密码字段从明文(self.cleaned_data["password1"]
)转成密文再存入数据库(下面详谈)
如果你不用这个 UserCreationForm
,要实现自己的注册逻辑,那么你可以参考它实现类似的字段校验,并且在存入数据库时也一样调用 set_password()
。
set_password()
的行为描述在 官方文档,具体如下:
使用默认配置时,Django 会采用 PBKDF2 算法及 SHA256 摘要算法,迭代 150000 次(Django 2.2)来生成密码:
- 生成一个密码学安全的随机字符串作为盐
- 使用 PBKDF2 算法,将密码原文、盐及迭代次数(默认 150000 次)作为参数,并以 SHA256 为摘要算法,生成一串 hash 值
- 以
"%s$%d$%s$%s" % (self.algorithm, iterations, salt, hash)
形式将密码保存至数据库(默认auth_user
表)
代码入口在 django.contrib.auth.base_user.AbstractBaseUser.set_password
。
代码比较冗长,不再列出。