7.1 认证系统与会话管理 本节摘要:Django 自带一套完整认证体系:用户模型、密码哈希、登录登出视图、会话管理。本节用内置组件搭出墨迹博客的登录系统,讲清 authenticate 与 login 的分工、会话 Cookie 的配合机制、以及扩展用户信息的 Profile 模式。绝大多数项目不该自己发明登录。 内置用户模型:先认识再扩展 django.contrib.auth 提供现成的用户模型,字段覆盖用户名、密码哈希、邮箱、两个布尔开关(是否员工、是否活跃)。它最值得信任的部分是密码管理: 数据库里存的是 PBKDF2 派生哈希加随机盐,算法参数记录在哈希串前缀里,将来升级算法,老用户下次登录时自动重哈希——这套机制是第 1 章"框架替你挡的箭"里最厚的一支。
本节摘要:Django 自带一套完整认证体系:用户模型、密码哈希、登录登出视图、会话管理。本节用内置组件搭出墨迹博客的登录系统,讲清 authenticate 与 login 的分工、会话 Cookie 的配合机制、以及扩展用户信息的 Profile 模式。绝大多数项目不该自己发明登录。
django.contrib.auth 提供现成的用户模型,字段覆盖用户名、密码哈希、邮箱、两个布尔开关(是否员工、是否活跃)。它最值得信任的部分是密码管理:
from django.contrib.auth.models import User user = User.objects.create_user("xiaomo", "x@moji.dev", "强密码123") # create_user 负责哈希;绝不要用 create 直接塞明文密码 user.set_password("新密码") # 改密码同样走哈希 user.check_password("新密码") # 校验而不接触哈希细节 → True
数据库里存的是 PBKDF2 派生哈希加随机盐,算法参数记录在哈希串前缀里,将来升级算法,老用户下次登录时自动重哈希——这套机制是第 1 章"框架替你挡的箭"里最厚的一支。
一个老问题:内置字段不够怎么办?两条路。简单场景用 Profile 模式外挂(本节末尾);确实要改核心字段(比如用邮箱登录)则在一开始就替换自定义用户模型。替换必须发生在第一次迁移之前,事后改造要迁移用户数据,代价极高——这是 Django 项目十大后悔榜的常客。
内置认证视图开箱即用,路由一行接入:
# 根路由 path("accounts/", include("django.contrib.auth.urls")),
自动获得登录、登出、密码修改、密码重置等一整套路由。要做的只是提供登录页模板(约定位置):
{# registration/login.html #} {% extends "base.html" %} {% block content %} <form method="post"> {% csrf_token %} {{ form.as_p }} <button type="submit">登录</button> </form> {% endblock %}
墨迹博客的作者登录后的跳转:
# settings 配置 LOGIN_REDIRECT_URL = "blog:article-list" # 登录成功去哪 LOGOUT_REDIRECT_URL = "blog:article-list" # 登出后去哪 LOGIN_URL = "login" # 未登录被拦时去哪
登出甚至不需要模板——LogoutView 收到请求即清除会话并跳转。若想自己写登录逻辑(比如加图形验证码),核心 API 是两步,分工必须分清:
from django.contrib.auth import authenticate, login def my_login(request): user = authenticate(request, username="...", password="...") # authenticate:只验证凭证,返回用户对象或 None,不带任何状态 if user is not None: login(request, user) # login:把用户写进会话,产生会话状态 return redirect("blog:article-list") ...
authenticate 认人,login 记人。只调前者页面毫无变化,只调后者会报错——这对 API 名字的辨析是面试与实战的双料高频题。
HTTP 请求彼此无记忆,会话机制补上这块短板。流程闭环如下:

会话默认存数据库(django_session 表),可切缓存或 Redis 后端提速。浏览一遍会话的操作接口:request.session 取值、set_expiry 设过期、flush 登出时清空。直接操作用户的 API 还有一对判断:is_authenticated 区分登录与否。注意它是属性不是方法,模板里 {{ user.is_authenticated }} 直接用。
用户的昵称、头像、签名不属于核心认证,外挂一个一对一模型最干净:
class Profile(models.Model): user = models.OneToOneField( User, on_delete=models.CASCADE, related_name="profile") nickname = models.CharField("昵称", max_length=50, blank=True) bio = models.TextField("签名", blank=True)
用信号(第 11 章展开)在建用户时自动建 Profile,之后模板里 {{ user.profile.nickname }} 一路点到底。一对一字段的妙处:反查唯一,user.profile 不存在时抛异常而不是返回列表,语义与"一人一份资料"严丝合缝。
💡 判断信息该进核心还是 Profile 的一条线:认证流程要用的进核心,展示用的进 Profile。拿不准的都放 Profile,将来合并易、拆分难的方向要选对。
会话机制建立起来后,它的安全面也要同步正视:会话 Cookie 就是用户的临时身份证,谁拿到它谁就是那个人。第一个要点是登出的彻底性——退出登录不仅要删服务端的会话记录,还要让浏览器端的 Cookie 失效,只做一半会导致看起来退出了、旧凭证仍可用的假安全。第二个要点是敏感操作的凭证刷新——修改密码、绑定邮箱这类操作之后,应当刷新会话标识,防止会话固定攻击:攻击者预设的会话标识在用户登录后必须作废重发。第三个要点是记住登录的取舍——延长会话寿命提升便利,也延长了凭证暴露的窗口,内容型站点给七天,涉及支付的场景给两小时,寿命应与数据敏感度挂钩。这些细节平时无声无息,出事时都是头版新闻。认证体系的安全审计,本质上就是围着这张临时身份证检查每一个流通环节。
认识了"你是谁",下一节约束"你能做什么":权限与分组。