(十五)数据库会话与带掩码的 CSRF 令牌
1. Cookie 编号与服务器会话
本项目的 reading_session Cookie 保存随机会话编号,用户编号和认证信息在 django_session 表中。会话中间件恢复会话,认证中间件通过认证后端取得 request.user。它与上一章默认把数据放进签名 Cookie 的方式不同,先看数据实际保存在哪里。
退出会删除当前数据库会话并清空浏览器 Cookie。即使保留退出前的编号,再发送也不能查回原身份。默认读取连接在请求结束时由 Django 管理收尾,不需要给每个视图手动写 sqlite3.close()。
2. 表单文本不必等于 CSRF Cookie
模板中的 {% csrf_token %} 生成隐藏字段,CSRF 中间件负责核对。Django 的表单令牌带有随机掩码,因此它与 reading_csrf Cookie 的原始文本不同;每次渲染还可以生成不同的隐藏文本,仍然对应同一份校验秘密。
登录会更新 CSRF 秘密。登录前页面的旧令牌继续提交时得到 403,刷新后的表单可以正常提交。带有效字段但来源不符的请求,也可能在 Origin 检查处被拒绝。
# 项目文件:demos/auth_steps.py
# 跟踪登录时的会话与 CSRF 变化,退出后再重放旧会话检查身份是否失效。
from lab import lab, token
with lab():
from django.test import Client
client = Client(enforce_csrf_checks=True)
hidden = token(client, "/accounts/login/")
original = client.cookies["reading_csrf"].value
print("隐藏字段等于 Cookie", hidden == original)
response = client.post(
"/accounts/login/",
{"username": "alice", "password": "learn-alice", "csrfmiddlewaretoken": hidden},
)
print(
"登录",
response.status_code,
"CSRF 已更新",
client.cookies["reading_csrf"].value != original,
)
print(
"旧令牌提交",
client.post(
"/books/new/", {"title": "旧表单", "csrfmiddlewaretoken": hidden}
).status_code,
)
previous = client.cookies["reading_session"].value
client.post("/accounts/logout/", {"csrfmiddlewaretoken": token(client, "/")})
replay = Client()
replay.cookies["reading_session"] = previous
print("退出后重放", replay.get("/api/books/").status_code)输出依次显示隐藏字段不等于 Cookie、登录 302 且令牌更新、旧令牌 403、退出后旧会话重放 401。应用读取实际表单并交给框架校验,不自行按字符串相等重写这套机制。详细过程见 Django CSRF。