Django自定义认证中间件引发sessionid重置、会话数据丢失问题
我基于django.contrib.auth.backends.RemoteUserBackend实现了自定义认证后端与中间件,仅当请求携带ID-TOKEN Cookie时允许用户登录。本地环境中自定义会话数据(如id_token_digest)功能正常,但生产环境下,HTTP响应始终返回Set-Cookie头重置sessionid,导致会话数据丢失。请问是否是中间件顺序问题,或有其他解决方案?
中间件代码
import hashlib import logging from django.conf import settings from django.contrib import auth from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.urls import reverse from django.utils.deprecation import MiddlewareMixin from main.auth.backends import MyRemoteUserBackend from main.auth.jwt import JWT logger = logging.getLogger(__name__) class AllowAllUsersMiddleware(MiddlewareMixin): def process_request(self, request): if not hasattr(request, "user"): raise ImproperlyConfigured( "Requires 'AuthenticationMiddleware' in middleware" ) try: id_token = request.COOKIES["ID-TOKEN"] except KeyError: if request.user.is_authenticated: self._remove_invalid_user(request) return token = JWT(id_token) id_token_digest = hashlib.sha256(id_token.encode(), usedforsecurity=False).hexdigest() has_id_token_digest_changed = id_token_digest != request.session.get("id_token_digest", "") if request.user.is_authenticated: if request.user.get_username() == token.username and not has_id_token_digest_changed: return # username and token did not change, do nothing self._remove_invalid_user(request) user = auth.authenticate(request, token=token) if user: request.user = user auth.login(request, user) request.session["id_token_digest"] = id_token_digest def _remove_invalid_user(self, request): auth.logout(request)
后端代码
from typing import Optional from django.conf import settings from django.contrib.auth import get_user_model from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import Group from main.auth.jwt import JWT UserModel = get_user_model() class MyRemoteUserBackend(ModelBackend): def authenticate(self, request, token: JWT) -> Optional[UserModel]: if not token: return user, _ = UserModel._default_manager.get_or_create(username=token.username) return user
生产环境配置
AUTHENTICATION_BACKENDS = [ "main.auth.backends.MyRemoteUserBackend", # custom backend ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "main.auth.middleware.AllowAllUsersMiddleware", # custom middleware "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] SESSION_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = "Strict"
Set-Cookie: sessionid=rlc...tn; expires=Mon, 03 Feb 2025 14:29:53 GMT; HttpOnly; Max-Age=1209600; Path=/; SameSite=Lax; Secure
1. 中间件顺序问题(核心原因)
当前自定义中间件AllowAllUsersMiddleware放在AuthenticationMiddleware之后,会触发以下问题:
AuthenticationMiddleware会先从session中读取并设置request.user- 你的中间件后续修改
request.user并调用auth.login时,Django会认为是新的用户登录,自动旋转sessionid(重置sessionid)以防范会话固定攻击,导致原有会话数据丢失
解决方案:将自定义认证中间件移到AuthenticationMiddleware之前,让你的认证逻辑先执行,AuthenticationMiddleware直接使用你设置好的request.user,避免重复认证和sessionid重置:
MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.locale.LocaleMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "main.auth.middleware.AllowAllUsersMiddleware", # 调整到AuthenticationMiddleware之前 "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ]
2. 避免不必要的auth.login调用
当前逻辑中,即使用户已认证仅token摘要变化,也会执行登出再登录,这会强制重置sessionid。可以优化逻辑:仅在用户切换或首次登录时调用auth.login,其余情况直接更新会话数据即可:
# 修改process_request中的用户认证判断逻辑 if request.user.is_authenticated: if request.user.get_username() == token.username: if not has_id_token_digest_changed: return # 无变化,无需操作 else: # 仅更新会话摘要,无需重新登录 request.session["id_token_digest"] = id_token_digest request.session.modified = True # 显式标记会话修改 return # 用户不匹配时才登出重新登录 self._remove_invalid_user(request)
3. 修复SameSite配置不一致
生产环境配置中SESSION_COOKIE_SAMESITE = "Strict",但返回的Set-Cookie头显示SameSite=Lax,说明配置未生效,可能是:
- 反向代理服务器(如Nginx)覆盖了Set-Cookie头的SameSite属性
- Django版本过低(低于2.1不支持SameSite配置)
解决方案:检查服务器配置,确保不修改Django返回的Set-Cookie头,或在服务器层面统一配置SameSite为Strict。
4. 显式标记会话修改
在中间件中修改session后,显式设置request.session.modified = True,确保Django会保存会话数据,避免因中间件执行顺序导致的会话数据丢失。
内容的提问来源于stack exchange,提问作者Q Caron

