一、Django认证系统概述
1.1 django.contrib.auth 模块
Django 内置了一套功能完备的用户认证系统,位于 django.contrib.auth 模块中。它是 Django 的"contrib"(贡献)包之一,默认包含在 INSTALLED_APPS 中。该模块由两个核心部分组成:认证(Authentication)和授权(Authorization)。认证解决的是"你是谁"的问题——验证用户的身份凭证(通常是用户名和密码);授权解决的是"你能做什么"的问题——确定已认证用户是否有权限执行特定操作。
认证系统由以下几个核心模型和工具构成:User 模型存储用户信息,Permission 模型定义权限,Group 模型管理用户组,以及一系列视图、表单和装饰器帮助快速实现登录、注销、注册和密码管理等功能。对于大多数 Web 应用而言,Django 内置的认证系统已经足够强大,开箱即用。
1.2 User 模型内置字段
Django 默认的 User 模型提供了以下核心字段:
| 字段名 | 类型 | 说明 | 是否必填 |
| username | CharField(150) | 用户名,唯一标识 | 是 |
| password | CharField | 密码哈希值 | 是 |
| email | EmailField | 电子邮箱 | 否 |
| first_name | CharField(150) | 名 | 否 |
| last_name | CharField(150) | 姓 | 否 |
| is_active | BooleanField | 是否激活 | 默认 True |
| is_staff | BooleanField | 是否可登录管理后台 | 默认 False |
| is_superuser | BooleanField | 是否超级用户 | 默认 False |
| date_joined | DateTimeField | 注册时间 | 自动设置 |
| last_login | DateTimeField | 最后登录时间 | 自动更新 |
1.3 AbstractUser vs AbstractBaseUser
当默认的 User 模型不满足需求时,Django 提供了两种自定义扩展方式:
AbstractUser:这是最常用的方式。它包含了默认 User 模型的所有字段和方法,你只需继承它并添加额外字段即可。适用于"在默认用户基础上增加字段"的场景,比如添加头像、手机号等。这种方式最简单,推荐大多数项目使用。
AbstractBaseUser:当需要彻底改变认证方式时使用,例如用邮箱代替用户名登录。它只包含密码相关的功能(password、last_login、is_active),你需要自己定义标识字段(如 email 作为 USERNAME_FIELD)。这种方式工作量较大,需要同时自定义 UserManager。通常只有在非常特殊的认证需求下才考虑使用。
最佳实践:除非有充分的理由,否则始终优先使用 AbstractUser 扩展而非 AbstractBaseUser。在项目初期就完成自定义用户模型的设置,因为 Django 要求在第一次迁移(migrate)之前定义好 AUTH_USER_MODEL。
二、用户管理
2.1 创建普通用户
Django 提供了便捷的 create_user 方法创建普通用户。与直接实例化 User 模型不同,create_user 会自动处理密码的哈希加密:
from django.contrib.auth.models import User
# 创建普通用户(密码会自动哈希加密)
user = User.objects.create_user(
username='zhangsan',
email='zhangsan@example.com',
password='secure_password_123'
)
# user 已创建成功,密码已加密保存
print(user.pk) # 输出用户ID
2.2 创建超级用户
超级用户拥有所有权限,可以访问 Django 管理后台。创建方式有两种:
命令行方式:
python manage.py createsuperuser
# 交互式输入用户名、邮箱和密码即可
编程方式:
from django.contrib.auth.models import User
# is_superuser=True 同时也会设置 is_staff=True
superuser = User.objects.create_superuser(
username='admin',
email='admin@example.com',
password='admin_pass_123'
)
print(superuser.is_superuser) # True
print(superuser.is_staff) # True
2.3 验证密码
Django 不存储明文密码,而是存储哈希值。验证密码需要使用 check_password 方法,而非直接比较字符串:
user = User.objects.get(username='zhangsan')
# 正确方式:使用 check_password 验证
if user.check_password('secure_password_123'):
print("密码正确")
else:
print("密码错误")
# 错误方式:不要直接比较哈希值
# if user.password == input_password: # 永远不要这样做!
2.4 更新用户信息与修改密码
更新用户信息时需要注意一个关键点:如果涉及密码修改,不能直接使用 save(),而必须使用 set_password() 方法,否则密码不会被正确地重新哈希:
# 更新普通字段(非密码)——可以直接修改并保存
user = User.objects.get(username='zhangsan')
user.email = 'newemail@example.com'
user.first_name = '三'
user.save() # 更新非密码字段没问题
# 修改密码——必须使用 set_password
user.set_password('new_secure_password_456')
user.save() # 密码会以新哈希值存储
# 批量创建用户时需要注意密码处理
# 错误方式:密码不会被哈希
wrong_user = User(username='lisi', password='plain_text')
wrong_user.save() # 密码以明文存储!
# 正确方式
correct_user = User(username='lisi')
correct_user.set_password('plain_text')
correct_user.save() # 密码已哈希
核心要点:永远不要直接给 User 模型的 password 字段赋值字符串。始终使用 create_user()、create_superuser() 或 set_password() 方法来处理密码。这确保了密码经过 Django 的密码哈希算法处理,保证安全性。
三、登录与注销
3.1 authenticate() 验证用户
authenticate() 函数负责验证用户凭据。它接收请求对象和凭据参数(通常是 username 和 password),如果验证通过则返回 User 对象,否则返回 None:
from django.contrib.auth import authenticate
def login_view(request):
username = request.POST['username']
password = request.POST['password']
# authenticate() 会检查凭据是否有效
user = authenticate(request, username=username, password=password)
if user is not None:
# 用户凭据有效
print("认证成功")
else:
# 用户凭据无效
print("用户名或密码错误")
3.2 login() 创建会话
login() 函数在 authenticate 验证通过后调用,负责在服务器端创建会话(session)并将用户 ID 写入其中。登录成功后,可以通过 request.user 访问当前用户:
from django.contrib.auth import authenticate, login
def login_view(request):
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
# 关键步骤:调用 login() 创建会话
login(request, user)
# 现在 request.user 就是当前登录用户
print(f"用户 {request.user.username} 已登录")
return redirect('home')
else:
return render(request, 'login.html', {'error': '无效的用户名或密码'})
3.3 logout() 注销
logout() 函数清除当前请求的会话数据,并刷新用户的会话(防止会话固定攻击):
from django.contrib.auth import logout
def logout_view(request):
logout(request) # 清除会话数据
# 此时 request.user 变为 AnonymousUser
return redirect('login')
3.4 完整的登录视图实现
以下是同时支持 GET(显示表单)和 POST(处理登录)的完整登录视图:
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
def user_login(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
# 支持 next 参数:登录后跳转到之前访问的页面
next_url = request.GET.get('next', 'home')
return redirect(next_url)
else:
messages.error(request, '用户名或密码错误')
return render(request, 'accounts/login.html')
def user_logout(request):
logout(request)
return redirect('login')
3.5 @login_required 装饰器
@login_required 装饰器是保护视图函数最简便的方式。未登录用户访问被保护的视图时,会被重定向到登录页面(通过 settings.LOGIN_URL 配置),并在 URL 后附加 ?next= 参数记录原始目标地址:
from django.contrib.auth.decorators import login_required
from django.conf import settings
# 配置文件中设置
# LOGIN_URL = '/accounts/login/' # 默认值
@login_required # 未登录用户会被重定向到 LOGIN_URL
def profile(request):
"""用户个人中心页面——仅登录用户可访问"""
return render(request, 'accounts/profile.html', {
'user': request.user
})
# 也可以指定特定的登录 URL
@login_required(login_url='/accounts/login/')
def settings(request):
return render(request, 'accounts/settings.html')
3.6 LoginRequiredMixin 类视图
对于基于类的视图(CBV),使用 LoginRequiredMixin 实现同样的保护效果。注意 LoginRequiredMixin 应放在继承列表的最左侧(MRO 优先级最高):
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView
class ProfileView(LoginRequiredMixin, TemplateView):
template_name = 'accounts/profile.html'
# 可选:自定义未登录时的重定向地址
login_url = '/accounts/login/'
# 可选:自定义重定向时附加的参数名(默认是 'next')
redirect_field_name = 'next'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user'] = self.request.user
return context
四、注册功能
4.1 UserCreationForm 内置注册表单
Django 提供了内置的 UserCreationForm,它包含三个字段:username(用户名)、password1(密码)、password2(确认密码),并已内置了密码强度校验和两次密码一致性验证:
from django.contrib.auth.forms import UserCreationForm
from django.shortcuts import render, redirect
def register(request):
if request.method == 'POST':
form = UserCreationForm(request.POST)
if form.is_valid():
form.save() # 创建用户
return redirect('login')
else:
form = UserCreationForm()
return render(request, 'accounts/register.html', {'form': form})
4.2 自定义注册表单
实际项目中往往需要收集更多信息,可以通过继承 UserCreationForm 来自定义:
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class CustomUserCreationForm(UserCreationForm):
email = forms.EmailField(
required=True,
label='邮箱',
widget=forms.EmailInput(attrs={'placeholder': '请输入邮箱'})
)
phone = forms.CharField(
max_length=11,
required=False,
label='手机号'
)
class Meta:
model = User
fields = ('username', 'email', 'phone', 'password1', 'password2')
def clean_email(self):
"""邮箱唯一性验证"""
email = self.cleaned_data.get('email')
if User.objects.filter(email=email).exists():
raise forms.ValidationError('该邮箱已被注册')
return email
def save(self, commit=True):
user = super().save(commit=False)
user.email = self.cleaned_data['email']
if commit:
user.save()
return user
4.3 注册后自动登录
用户注册完成后,通常会希望直接进入已登录状态,而无需再次输入密码:
from django.contrib.auth import login, authenticate
from django.shortcuts import render, redirect
def register_with_auto_login(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save()
# 注册成功后自动登录
# 使用用户注册时填写的凭据进行认证
username = form.cleaned_data.get('username')
password = form.cleaned_data.get('password1')
authenticated_user = authenticate(
request,
username=username,
password=password
)
login(request, authenticated_user)
return redirect('home')
else:
form = CustomUserCreationForm()
return render(request, 'accounts/register.html', {'form': form})
五、权限管理
5.1 权限三要素
Django 的权限系统由三个核心概念组成:
用户(User):权限的最终载体,每个用户都有一组关联权限。
组(Group):权限的中间容器,将权限赋予组,再将用户加入组,用户即可继承组的所有权限。
权限(Permission):最小的授权单位,控制能否执行某项操作。Django 默认为每个模型自动生成 4 种基础权限:"add"、"change"、"delete" 和 "view"。
5.2 Permission 模型
权限由 Permission 模型表示,包含以下字段:
from django.contrib.auth.models import Permission
# 权限对象的字段
perm = Permission.objects.get(codename='add_article')
print(perm.name) # "Can add article"(人类可读的名称)
print(perm.codename) # "add_article"(代码中使用的名称)
print(perm.content_type) # 关联的内容类型(哪个模型)
# 查看某模型的所有默认权限
from django.contrib.auth.models import User
content_type = ContentType.objects.get_for_model(Article)
permissions = Permission.objects.filter(content_type=content_type)
for p in permissions:
print(f"{p.codename}: {p.name}")
5.3 @permission_required 装饰器
在函数视图中检查用户是否拥有特定权限:
from django.contrib.auth.decorators import permission_required
# 检查用户是否有 "blog.add_article" 权限
# 格式:app_label.codename
@permission_required('blog.add_article')
def create_article(request):
# 只有拥有权限的用户才能访问
return render(request, 'blog/create_article.html')
# 未满足权限时的处理
@permission_required(
'blog.change_article',
login_url='/accounts/login/', # 未登录跳转
raise_exception=True # 已登录但无权限时返回 403
)
def edit_article(request, article_id):
return render(request, 'blog/edit_article.html')
# 检查多个权限(需同时拥有)
@permission_required('blog.add_comment')
@permission_required('blog.change_comment')
def moderate_comment(request):
return render(request, 'blog/moderate.html')
5.4 PermissionRequiredMixin 类视图
基于类的视图中使用 PermissionRequiredMixin:
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.views.generic import CreateView
class CreateArticleView(PermissionRequiredMixin, CreateView):
model = Article
fields = ['title', 'content']
template_name = 'blog/create_article.html'
success_url = '/articles/'
# 必须拥有的权限(可以是列表)
permission_required = 'blog.add_article'
# 如果同时需要多个权限
# permission_required = ['blog.add_article', 'blog.can_publish']
# 当用户没有权限时
# permission_denied_message = '您没有创建文章的权限'
# raise_exception = True # 返回 403 而非重定向
5.5 自定义模型权限
在模型的 Meta 类中通过 permissions 属性自定义业务权限:
class Article(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
status = models.CharField(max_length=20, default='draft')
author = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
permissions = [
('can_publish', '可以发布文章'), # codename, name
('can_approve', '可以审核文章'),
('can_feature', '可以推荐文章到首页'),
]
# 默认权限仍然会自动生成
# default_permissions = ('add', 'change', 'delete', 'view')
# 创建自定义权限后,需要运行 makemigrations 和 migrate
# 然后即可像内置权限一样使用:
# @permission_required('blog.can_publish')
5.6 编程式权限检查
在代码中动态检查用户是否拥有权限:
# user.has_perm() —— 检查单个权限
if request.user.has_perm('blog.can_publish'):
# 用户拥有发布权限
article.status = 'published'
article.save()
else:
messages.error(request, '您没有发布文章的权限')
# user.has_perms() —— 同时检查多个权限
if request.user.has_perms(['blog.add_article', 'blog.can_publish']):
print("用户拥有所有指定权限")
# 直接分配权限给用户
from django.contrib.auth.models import Permission
perm = Permission.objects.get(codename='can_publish')
request.user.user_permissions.add(perm) # 添加权限
# request.user.user_permissions.remove(perm) # 移除权限
# request.user.user_permissions.clear() # 清除所有权限
# 用户权限缓存——修改权限后需重新获取用户对象
# user = User.objects.get(pk=user.pk) # 重新查询以刷新缓存
六、组(Group)管理
6.1 Group 模型与权限分配
组是将权限组织在一起的有效方式。通过组来管理权限,可以避免逐个为用户分配权限的繁琐操作,尤其适用于用户数量较多的系统:
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
# 创建新组
editor_group = Group.objects.create(name='编辑')
# 获取权限对象
content_type = ContentType.objects.get_for_model(Article)
add_perm = Permission.objects.get(
content_type=content_type, codename='add_article'
)
change_perm = Permission.objects.get(
content_type=content_type, codename='change_article'
)
# 将权限分配给组
editor_group.permissions.add(add_perm, change_perm)
# 将用户加入组
user = User.objects.get(username='zhangsan')
user.groups.add(editor_group)
# 此时用户自动拥有 "add_article" 和 "change_article" 权限
# user.has_perm('blog.add_article') # True
6.2 组的应用场景
| 角色 | 分配的权限 | 说明 |
| 读者 | view_article | 仅可查看文章 |
| 作者 | add_article, change_article (own) | 创建和编辑自己的文章 |
| 编辑 | change_article, delete_article, can_publish | 审核、编辑、发布文章 |
| 管理员 | 所有权限 | 完全控制系统 |
# 实际项目中的角色初始化(在数据库初始化脚本中执行)
from django.contrib.auth.models import Group, Permission
def init_groups():
"""初始化用户角色组"""
groups_permissions = {
'读者': ['view_article'],
'作者': ['add_article', 'change_article', 'view_article'],
'编辑': ['change_article', 'delete_article',
'can_publish', 'view_article'],
}
for group_name, perms in groups_permissions.items():
group, created = Group.objects.get_or_create(name=group_name)
for perm_codename in perms:
try:
perm = Permission.objects.get(codename=perm_codename)
group.permissions.add(perm)
except Permission.DoesNotExist:
print(f"权限 {perm_codename} 不存在")
七、自定义用户模型
7.1 继承 AbstractUser 扩展字段
这是最常用且最推荐的自定义方式,适用于需要在默认用户基础上增加额外字段的场景:
# models.py
from django.contrib.auth.models import AbstractUser
from django.db import models
class CustomUser(AbstractUser):
"""自定义用户模型——在默认用户基础上扩展"""
# 新增字段
phone = models.CharField(max_length=11, unique=True, verbose_name='手机号')
avatar = models.ImageField(
upload_to='avatars/',
null=True,
blank=True,
verbose_name='头像'
)
bio = models.TextField(max_length=500, blank=True, verbose_name='个人简介')
birthday = models.DateField(null=True, blank=True, verbose_name='出生日期')
gender = models.CharField(
max_length=10,
choices=[('M', '男'), ('F', '女'), ('O', '其他')],
default='O'
)
# 修改默认的字段属性
email = models.EmailField(unique=True) # 邮箱设为唯一
def __str__(self):
return self.username
class Meta:
verbose_name = '用户'
verbose_name_plural = '用户'
# settings.py —— 配置自定义用户模型
AUTH_USER_MODEL = 'accounts.CustomUser'
# 注意:必须在第一次运行 migrate 之前设置 AUTH_USER_MODEL
# 否则需要重建数据库(因为 Django 的权限系统与 User 模型紧密耦合)
# admin.py —— 管理后台需要配套的自定义配置
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin
from .models import CustomUser
class CustomUserAdmin(UserAdmin):
"""自定义用户管理后台"""
fieldsets = UserAdmin.fieldsets + (
('额外信息', {'fields': ('phone', 'avatar', 'bio', 'birthday', 'gender')}),
)
add_fieldsets = UserAdmin.add_fieldsets + (
('额外信息', {'fields': ('phone', 'email', 'birthday', 'gender')}),
)
admin.site.register(CustomUser, CustomUserAdmin)
7.2 继承 AbstractBaseUser 完全自定义
当需要彻底改变认证方式(如使用邮箱登录)时使用此方式:
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin
from django.db import models
class CustomUserManager(BaseUserManager):
"""自定义用户管理器——必须与 AbstractBaseUser 配对使用"""
def create_user(self, email, password=None, **extra_fields):
"""创建普通用户"""
if not email:
raise ValueError('邮箱地址为必填项')
email = self.normalize_email(email)
user = self.model(email=email, **extra_fields)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password=None, **extra_fields):
"""创建超级用户"""
extra_fields.setdefault('is_staff', True)
extra_fields.setdefault('is_superuser', True)
if extra_fields.get('is_staff') is not True:
raise ValueError('超级用户必须设置 is_staff=True')
if extra_fields.get('is_superuser') is not True:
raise ValueError('超级用户必须设置 is_superuser=True')
return self.create_user(email, password, **extra_fields)
class EmailUser(AbstractBaseUser, PermissionsMixin):
"""以邮箱作为唯一标识的用户模型"""
email = models.EmailField(unique=True, verbose_name='邮箱')
nickname = models.CharField(max_length=50, verbose_name='昵称')
is_active = models.BooleanField(default=True, verbose_name='是否激活')
is_staff = models.BooleanField(default=False, verbose_name='是否员工')
date_joined = models.DateTimeField(auto_now_add=True, verbose_name='注册时间')
# 用 email 字段代替 username 作为登录标识
USERNAME_FIELD = 'email'
# 创建超级用户时必须提供的额外字段(不含 USERNAME_FIELD 和 password)
REQUIRED_FIELDS = ['nickname']
# 指定自定义管理器
objects = CustomUserManager()
def __str__(self):
return self.email
class Meta:
verbose_name = '邮箱用户'
verbose_name_plural = '邮箱用户'
7.3 自定义注意事项
自定义用户模型时有几个重要的注意事项:
第一,务必在第一次执行 migrate 之前设置 AUTH_USER_MODEL。如果在创建数据库表后再修改用户模型,Django 会报错,因为认证系统的数据库关系已经建立,修改会导致外键和权限关联断裂。解决方法是重置数据库或使用自定义迁移。
第二,在使用 AbstractBaseUser 时,必须同时自定义 BaseUserManager,并实现 create_user 和 create_superuser 方法。管理器中还需要使用 normalize_email 等方法保证数据的一致性。
第三,所有引用了 User 模型的地方,都应使用 settings.AUTH_USER_MODEL(在模型定义中)或 get_user_model()(在代码中),而非直接导入 User 模型。这样可以确保自定义用户模型被正确使用。
# 引用自定义用户模型的正确方式
# 方式一:模型定义中使用 settings.AUTH_USER_MODEL
from django.conf import settings
class Article(models.Model):
author = models.ForeignKey(
settings.AUTH_USER_MODEL, # 推荐:延迟引用,避免循环导入
on_delete=models.CASCADE
)
# 方式二:代码中使用 get_user_model()
from django.contrib.auth import get_user_model
User = get_user_model() # 返回当前激活的用户模型
users = User.objects.all()
八、密码管理
8.1 密码哈希算法
Django 默认使用 PBKDF2 算法对密码进行哈希处理,并自动管理盐值(salt)。密码哈希值的存储格式为 <algorithm>$<iterations>$<salt>$<hash>,例如:
pbkdf2_sha256$720000$xxxxxxxxxxxx$xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# ├── 算法名 ├── 迭代次数├── 盐值 ├── 哈希值
Django 的密码哈希系统是可插拔的。在 settings.py 中可以通过 PASSWORD_HASHERS 配置支持的哈希算法列表:
# settings.py —— 密码哈希器配置
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.PBKDF2PasswordHasher', # 默认,安全可靠
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher', # 更安全但更慢
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.ScryptPasswordHasher',
]
# Django 验证密码时会按列表顺序尝试匹配
# 当你升级迭代次数时,用户登录时会自动升级哈希值
8.2 密码验证器
Django 提供了密码验证器机制,用于在用户设置密码时检查密码强度。在 settings.py 中配置 AUTH_PASSWORD_VALIDATORS:
# settings.py
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
'OPTIONS': {
'max_similarity': 0.7, # 密码与用户属性相似度阈值
}
},
{
'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',
},
]
在代码中手动调用验证器:
from django.contrib.auth import password_validation
# 注册或修改密码时手动校验
try:
password_validation.validate_password(
password='user_input_password',
user=user # 可选:UserSimilarityValidator 需要
)
except password_validation.ValidationError as e:
# e.messages 包含所有错误信息列表
for msg in e.messages:
print(f"密码验证失败: {msg}")
else:
user.set_password('user_input_password')
user.save()
8.3 密码重置流程
Django 内置了完整的密码重置流程,包含以下四个步骤:
| 步骤 | 视图 | 模板 | 说明 |
| 1 | PasswordResetView | registration/password_reset_form.html | 用户输入注册邮箱 |
| 2 | PasswordResetDoneView | registration/password_reset_done.html | 提示邮件已发送 |
| 3 | PasswordResetConfirmView | registration/password_reset_confirm.html | 用户点击链接设置新密码 |
| 4 | PasswordResetCompleteView | registration/password_reset_complete.html | 密码重置成功提示 |
# urls.py —— 直接使用内置视图实现密码重置
from django.urls import path
from django.contrib.auth import views as auth_views
urlpatterns = [
# 密码重置
path('password-reset/',
auth_views.PasswordResetView.as_view(
template_name='accounts/password_reset.html',
email_template_name='accounts/password_reset_email.html',
subject_template_name='accounts/password_reset_subject.txt'
),
name='password_reset'),
path('password-reset/done/',
auth_views.PasswordResetDoneView.as_view(
template_name='accounts/password_reset_done.html'
),
name='password_reset_done'),
path('password-reset///',
auth_views.PasswordResetConfirmView.as_view(
template_name='accounts/password_reset_confirm.html'
),
name='password_reset_confirm'),
path('password-reset/complete/',
auth_views.PasswordResetCompleteView.as_view(
template_name='accounts/password_reset_complete.html'
),
name='password_reset_complete'),
]
# settings.py —— 密码重置邮件配置
# 开发环境:将重置链接输出到控制台
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# 生产环境:使用真实 SMTP 服务
# EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
# EMAIL_HOST = 'smtp.gmail.com'
# EMAIL_PORT = 587
# EMAIL_USE_TLS = True
# EMAIL_HOST_USER = 'your-email@gmail.com'
# EMAIL_HOST_PASSWORD = 'your-password'
# 密码重置相关配置
DEFAULT_FROM_EMAIL = 'noreply@example.com' # 发件人地址
PASSWORD_RESET_TIMEOUT = 3600 # 重置链接有效期(秒),默认 3 天
8.4 密码修改视图
Django 同样提供了内置的密码修改视图,允许已登录用户更改密码:
# urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
urlpatterns = [
# 密码修改(需要用户已登录)
path('password-change/',
auth_views.PasswordChangeView.as_view(
template_name='accounts/password_change.html',
success_url='/accounts/password-change/done/'
),
name='password_change'),
path('password-change/done/',
auth_views.PasswordChangeDoneView.as_view(
template_name='accounts/password_change_done.html'
),
name='password_change_done'),
]
# 手动实现密码修改
@login_required
def change_password(request):
if request.method == 'POST':
old_password = request.POST['old_password']
new_password = request.POST['new_password']
# 先验证旧密码是否正确
if request.user.check_password(old_password):
# 验证新密码强度
try:
password_validation.validate_password(new_password, request.user)
except ValidationError as e:
return render(request, 'accounts/change_password.html', {
'errors': e.messages
})
# 设置新密码
request.user.set_password(new_password)
request.user.save()
# 重要:修改密码后会话会被失效,需要重新登录
update_session_auth_hash(request, request.user)
messages.success(request, '密码修改成功')
return redirect('profile')
else:
messages.error(request, '旧密码不正确')
return render(request, 'accounts/change_password.html')
关键提示:使用 set_password() 修改密码后,Django 会更新会话中的哈希值,导致当前用户的会话失效。必须在修改密码后调用 update_session_auth_hash(request, user),否则用户会被强制登出。
九、核心要点总结
Django 用户认证系统是一套完整而强大的身份管理解决方案,以下是需要重点掌握的核心知识点:
模型层:默认 User 模型适用于大部分场景;需要扩展时优先选择继承 AbstractUser;仅在认证方式根本性改变时才使用 AbstractBaseUser。务必在首次 migrate 前通过 AUTH_USER_MODEL 指定自定义用户模型。
认证与会话:authenticate() 验证凭据,login() 创建会话,logout() 销毁会话。这三个函数构成了 Django 认证的核心流程。注意区分 authenticate(验证)和 login(登录)是两个独立步骤。
权限系统:利用内置的 Group 和 Permission 模型可以构建灵活的权限体系。@permission_required 装饰器和 PermissionRequiredMixin 提供了方便的保护机制。编程式权限检查使用 user.has_perm() 方法。
密码安全:永远不要直接操作 password 字段。使用 create_user()、set_password() 等方法确保密码正确哈希。配置 AUTH_PASSWORD_VALIDATORS 提升系统安全性。修改密码后调用 update_session_auth_hash 保持登录状态。
内置视图:Django 提供了 PasswordResetView、PasswordChangeView 等开箱即用的视图,只需编写对应的模板即可快速实现完整的密码管理流程。
十、进一步思考
掌握了 Django 认证系统的基础后,可以进一步探索以下进阶主题:
JWT 认证:对于前后端分离的 SPA 应用或移动端 API,传统的 Session 认证不再适用,需要学习使用 JWT(JSON Web Token)进行无状态认证。djangorestframework-simplejwt 是 Django REST Framework 生态中最流行的 JWT 解决方案。
OAuth2.0 社交登录:集成第三方登录(微信、GitHub、Google 等)可以使用 django-allauth 或 social-auth-app-django 等成熟库。这些库处理了 OAuth2.0 协议的复杂细节,提供了开箱即用的社交登录功能。
多因素认证:针对高安全性要求的场景(如管理后台、支付系统),可以引入双因素认证(2FA)机制,结合 TOTP(基于时间的一次性密码)或短信验证码来增强账户安全性。
认证性能优化:对于高并发应用,Session 认证的数据库查询可能成为瓶颈。可以考虑使用 Redis 等缓存系统存储 Session,或引入 CDN 级别的缓存策略来优化认证性能。