Django用户认证系统

Web开发专题 · 掌握Django完整的用户认证方案

专题:Python Web开发系统学习

关键词:Python, Web开发, Django认证, 用户管理, 权限控制, login_required, AbstractUser, 组权限

一、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 模型提供了以下核心字段:

字段名类型说明是否必填
usernameCharField(150)用户名,唯一标识
passwordCharField密码哈希值
emailEmailField电子邮箱
first_nameCharField(150)
last_nameCharField(150)
is_activeBooleanField是否激活默认 True
is_staffBooleanField是否可登录管理后台默认 False
is_superuserBooleanField是否超级用户默认 False
date_joinedDateTimeField注册时间自动设置
last_loginDateTimeField最后登录时间自动更新

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_usercreate_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 内置了完整的密码重置流程,包含以下四个步骤:

步骤视图模板说明
1PasswordResetViewregistration/password_reset_form.html用户输入注册邮箱
2PasswordResetDoneViewregistration/password_reset_done.html提示邮件已发送
3PasswordResetConfirmViewregistration/password_reset_confirm.html用户点击链接设置新密码
4PasswordResetCompleteViewregistration/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 级别的缓存策略来优化认证性能。

本学习笔记为本人学习资料,不得转载