Python 现代开发学习笔记

(十五)数据库会话与带掩码的 CSRF 令牌

1. Cookie 编号与服务器会话

本项目的 reading_session Cookie 保存随机会话编号,用户编号和认证信息在 django_session 表中。会话中间件恢复会话,认证中间件通过认证后端取得 request.user。它与上一章默认把数据放进签名 Cookie 的方式不同,先看数据实际保存在哪里。

退出会删除当前数据库会话并清空浏览器 Cookie。即使保留退出前的编号,再发送也不能查回原身份。默认读取连接在请求结束时由 Django 管理收尾,不需要给每个视图手动写 sqlite3.close()。

浏览器只带 reading_session 编号,服务器查 django_session 恢复认证信息并得到 request.user,退出删除记录后旧编号重放失效

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。

同一 CSRF 秘密生成不同带掩码的隐藏字段,中间件还原并核对,登录替换秘密后旧表单被拒绝,新表单重新取得有效令牌

笔记正文与全部题解 · Ctrl / ⌘ K