bigywater.tech
Django · 71

Django 用户认证流程

从请求进来到 request.user 有值,中间发生了什么?authenticate 和 login 各做什么?session 里到底存了什么?这篇用查询捕获和信号,把认证的每一步展开。

0 或 2
request.user 惰性:不访问 0 次查询;已登录访问一次 2 次(session + user)
3 个键
session 里的认证信息只有用户 id、后端路径、密码派生的 hash
凭据被打码
user_login_failed 信号里 credentials 的密码与 token 值被替换成星号,用户名保留

request.user 的惰性

实测 · 本机执行输出
匿名、视图没访问 request.user:查询 0
已登录、视图没访问 request.user:查询 0 []
已登录、视图访问 request.user:查询 2
    SELECT "django_session"."session_key", "django_session"."session_data", "django_session"."expir
    SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_supe
   → {'user': 'u', 'auth': True}
匿名(没有 sessionid cookie)访问 request.user:查询 0

AuthenticationMiddleware 只是给 request.user 放了一个惰性对象。视图不访问就不会有任何数据库开销;访问时先读 session、再按 id 取用户。

authenticate()

实测 · 本机执行输出
密码错误:返回 None  查询: ['SELECT "auth_user"."id", "auth_user"."password", "auth_user"."last_login", "auth_user"."is_supe']  信号: [('failed', {'username': 'u', 'password': '********************'})]
密码对:返回 u  信号: []
多余参数 foo=bar:返回 u  ← 不同后端接收不同参数,ModelBackend 收下多余的 **kwargs
不带 username/password(别的凭据):返回 None  信号: [('failed', {'token': '********************'})]
is_active=False 的用户密码正确: None (ModelBackend 拒绝不活跃用户)

它只负责「凭据 → 用户」,不改变 session。失败信号里的凭据是打过码的(实测:password 与 token 都是 20 个星号,username 保留),所以信号接收者写日志也不会泄露密码。

登录后的 session

实测 · 本机执行输出
session 内容的键: ['_auth_user_backend', '_auth_user_hash', '_auth_user_id']
_auth_user_id = 3  _auth_user_backend = django.contrib.auth.backends.ModelBackend
_auth_user_hash 长度 = 64 (来自密码哈希的派生值)

下一篇(第 72 篇)看 session 本身怎么存,第 73 篇看 login() 的内部,第 80 篇看这个 hash 的作用。

分步演示:认证的三步

认证流程
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / Python 3.14.7 / Django 6.1.1(默认用 SQLite,其它数据库会在文中注明),用 django.test.Client 或真实的本机服务器进程实测;计时用 time.perf_counter,数字每次运行会有小幅抖动。SQLite;用 django.test.Client 与 CaptureQueriesContext
    • 没有验证:多个 AUTHENTICATION_BACKENDS 的顺序(第 76 篇)、异步认证(aauthenticate)、RemoteUserMiddleware、哪些键名会被打码的完整规则(实测了 password 与 token)
    京ICP备2026067502号-1京公网安备11010502063487号