70·模块十三:Web开发基础高级

Django 用户认证系统

djangoauthenticationauthorization

第 70 课:Django 用户认证系统

所属模块:模块十三:Web开发基础 难度:高级 标签:django, authentication, authorization 上一课:Django 表单与表单处理 下一课:使用 Django REST framework 构建 API

学习目标

通过本课学习,你将能够:

  1. 理解Django内置用户认证系统的核心概念和组件。
  2. 实现用户注册、登录、登出功能。
  3. 使用@login_requiredPermissionRequiredMixin等装饰器/混入保护视图。
  4. 自定义User模型以扩展用户信息。
  5. 区分认证(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变量存在时,在表单上方显示错误信息。 预期输出: 输入错误密码时,登录页面应显示"用户名或密码错误"。

练习二(进阶): 实现一个"个人资料"页面,只有登录用户才能访问,并显示其扩展信息(如个人网站)。

要求

  1. 创建一个需要@login_required装饰的视图。
  2. 在模板中展示request.userrequest.user.website
  3. 在导航栏中,如果用户已登录则显示"个人资料"和"登出"链接,否则显示"登录"和"注册"。

练习三(综合): 尝试为博客文章模型(Article)设置权限。只有拥有blog.change_article权限的用户才能编辑文章。

要求

  1. ArticleCreateView的示例基础上,创建一个ArticleUpdateView
  2. 使用PermissionRequiredMixin,设置permission_required = 'blog.change_article'
  3. 创建一个编辑表单,并在视图中处理更新逻辑。

常见错误

  1. 忘记密码哈希: 千万不要使用明文存储密码!UserCreationFormset_password()方法会自动处理密码哈希。

    # 错误!
    user.password = 'plaintext'
    # 正确!
    user.set_password('secure_password')
    user.save()
    
  2. 装饰器顺序错误: 当同时使用多个装饰器时,@login_required应该放在最外层。

    # 正确顺序
    @login_required
    @permission_required('auth.change_user')
    def my_view(request): ...
    
    # 基于类的视图,混入的顺序也重要,通常 LoginRequiredMixin 放在最左边
    
  3. 在视图中直接修改用户密码: 应使用set_password()方法,它会自动哈希密码。直接赋值会存储明文或无效哈希。

    user.set_password('new_secure_password')
    user.save()
    # 注意:修改密码后,用户会被自动登出,需要重新登录。
    
  4. 忽略CSRF保护: 在POST表单中,必须添加{% csrf_token %},否则会收到403 Forbidden错误。Django默认的登录/注销视图已经处理了CSRF,但自定义表单必须手动添加。

小结

在本课中,我们深入探索了Django强大的用户认证系统:

  • 认证 vs 授权: 认证是验证身份,授权是检查权限。
  • 核心组件auth模块、User模型、authenticate()login()logout()函数以及@login_required装饰器。
  • 流程实现: 通过UserCreationForm实现安全的用户注册,通过authenticatelogin函数实现登录,并通过logout实现登出。
  • 访问控制: 使用@login_requiredPermissionRequiredMixin轻松保护视图和功能。
  • 模型扩展: 通过继承AbstractUser可以自由扩展用户模型,添加你需要的字段。

Django的认证系统是开箱即用且高度可定制的,从简单的个人项目到复杂的商业应用,它都能提供坚实的基础。下一步,我们将学习如何用Django REST framework为这些认证用户提供API服务。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「使用 Django REST framework 构建 API」 以巩固所学知识。