第 70 课:Django 用户认证系统
所属模块:模块十三:Web开发基础 难度:高级 标签:django, authentication, authorization 上一课:Django 表单与表单处理 下一课:使用 Django REST framework 构建 API
学习目标
通过本课学习,你将能够:
- 理解Django内置用户认证系统的核心概念和组件。
- 实现用户注册、登录、登出功能。
- 使用
@login_required和PermissionRequiredMixin等装饰器/混入保护视图。 - 自定义User模型以扩展用户信息。
- 区分认证(Authentication)与授权(Authorization)。
核心概念
想象一下,一个网站没有"门",谁都可以进入后台修改数据,这非常危险。用户认证(Authentication)就是回答"你是谁?"的过程(比如通过用户名和密码验证身份)。授权(Authorization)则是回答"你能做什么?"的过程(比如普通用户只能看,管理员才能删改)。
Django 提供了一个强大、安全且灵活的认证系统,它默认将用户信息存储在 auth_user 表中,并内置了密码哈希、会话管理、组和权限等高级功能。我们不需要重新发明轮子。
核心组件:
django.contrib.auth: 这是认证系统的核心应用。User模型: 存储用户基础信息(用户名、密码、邮箱、权限等)。authenticate()和login(): 验证用户凭据和创建会话的函数。@login_required: 一个视图装饰器,确保只有登录用户才能访问该视图。PermissionRequiredMixin: 一个基于类的视图混入,用于检查用户是否拥有特定权限。AbstractUser: 用于扩展默认User模型的基类。
代码示例
我们将构建一个简单的博客系统,演示用户认证的完整流程。
第一步:项目设置与模型
# settings.py
# 确保以下应用已添加
INSTALLED_APPS = [
# ...
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions', # 会话管理依赖于此
# ...
'blog', # 我们自己的应用
]
# 如果需要自定义User模型,在应用的 models.py 中定义后,必须在此设置
# AUTH_USER_MODEL = 'blog.MyUser'
第二步:自定义User模型(扩展默认模型)
# blog/models.py
from django.db import models
from django.contrib.auth.models import AbstractUser
# 扩展默认的 User 模型,添加个人网站字段
class MyUser(AbstractUser):
# AbstractUser 已经包含了 username, email, password 等所有基础字段
website = models.URLField(max_length=200, blank=True, verbose_name='个人网站')
class Meta:
verbose_name = '用户'
verbose_name_plural = verbose_name
def __str__(self):
return self.username
第三步:用户注册视图与表单
# blog/forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from .models import MyUser
# 继承 UserCreationForm 以快速创建包含密码验证的注册表单
class CustomUserCreationForm(UserCreationForm):
class Meta:
model = MyUser
fields = ('username', 'email', 'website',) # 包含我们自定义的字段
# blog/views.py
from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate, logout
from django.contrib.auth.decorators import login_required
from django.views import View
from .forms import CustomUserCreationForm
def register_view(request):
if request.method == 'POST':
form = CustomUserCreationForm(request.POST)
if form.is_valid():
user = form.save() # 保存用户到数据库
# 登录用户并重定向到首页
login(request, user)
return redirect('home')
else:
form = CustomUserCreationForm()
return render(request, 'blog/register.html', {'form': form})
def login_view(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)
return redirect('home') # 假设有一个名为 'home' 的URL
else:
error_message = "用户名或密码错误"
return render(request, 'blog/login.html', {'error': error_message})
return render(request, 'blog/login.html')
@login_required # 只有登录用户才能访问此视图
def logout_view(request):
logout(request)
return redirect('home')
# 基于类的视图,使用权限混入
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
class ArticleCreateView(LoginRequiredMixin, PermissionRequiredMixin, View):
# LoginRequiredMixin 确保用户登录
# PermissionRequiredMixin 检查权限
permission_required = 'blog.add_article' # app_label.permission_codename
raise_exception = True # 权限不足时抛出403错误,而不是重定向到登录页
def get(self, request):
# 处理GET请求,显示创建表单
return render(request, 'blog/create_article.html')
第四步:URL配置
# blog/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('register/', views.register_view, name='register'),
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
path('article/create/', views.ArticleCreateView.as_view(), name='article-create'),
# ... 其他URL
]
第五步:模板片段
<!-- templates/blog/register.html -->
<form method="post">
{% csrf_token %} <!-- Django 防止跨站请求伪造攻击的令牌 -->
{{ form.as_p }}
<button type="submit">注册</button>
</form>
实践练习
练习一(基础): 扩展上述代码,为login_view添加一个模板,在登录失败时显示error_message。
要求: 创建blog/templates/blog/login.html模板,包含用户名、密码输入框和提交按钮。当error变量存在时,在表单上方显示错误信息。
预期输出: 输入错误密码时,登录页面应显示"用户名或密码错误"。
练习二(进阶): 实现一个"个人资料"页面,只有登录用户才能访问,并显示其扩展信息(如个人网站)。
要求:
- 创建一个需要
@login_required装饰的视图。 - 在模板中展示
request.user和request.user.website。 - 在导航栏中,如果用户已登录则显示"个人资料"和"登出"链接,否则显示"登录"和"注册"。
练习三(综合): 尝试为博客文章模型(Article)设置权限。只有拥有blog.change_article权限的用户才能编辑文章。
要求:
- 在
ArticleCreateView的示例基础上,创建一个ArticleUpdateView。 - 使用
PermissionRequiredMixin,设置permission_required = 'blog.change_article'。 - 创建一个编辑表单,并在视图中处理更新逻辑。
常见错误
-
忘记密码哈希: 千万不要使用明文存储密码!
UserCreationForm和set_password()方法会自动处理密码哈希。# 错误! user.password = 'plaintext' # 正确! user.set_password('secure_password') user.save() -
装饰器顺序错误: 当同时使用多个装饰器时,
@login_required应该放在最外层。# 正确顺序 @login_required @permission_required('auth.change_user') def my_view(request): ... # 基于类的视图,混入的顺序也重要,通常 LoginRequiredMixin 放在最左边 -
在视图中直接修改用户密码: 应使用
set_password()方法,它会自动哈希密码。直接赋值会存储明文或无效哈希。user.set_password('new_secure_password') user.save() # 注意:修改密码后,用户会被自动登出,需要重新登录。 -
忽略CSRF保护: 在POST表单中,必须添加
{% csrf_token %},否则会收到403 Forbidden错误。Django默认的登录/注销视图已经处理了CSRF,但自定义表单必须手动添加。
小结
在本课中,我们深入探索了Django强大的用户认证系统:
- 认证 vs 授权: 认证是验证身份,授权是检查权限。
- 核心组件:
auth模块、User模型、authenticate()、login()、logout()函数以及@login_required装饰器。 - 流程实现: 通过
UserCreationForm实现安全的用户注册,通过authenticate和login函数实现登录,并通过logout实现登出。 - 访问控制: 使用
@login_required和PermissionRequiredMixin轻松保护视图和功能。 - 模型扩展: 通过继承
AbstractUser可以自由扩展用户模型,添加你需要的字段。
Django的认证系统是开箱即用且高度可定制的,从简单的个人项目到复杂的商业应用,它都能提供坚实的基础。下一步,我们将学习如何用Django REST framework为这些认证用户提供API服务。