Django限制单账号单登录时出现会话异常与UpdateError问题求助
问题原因排查
三个问题的核心根源如下:
- 多余的
request.session.save()调用:Django的login()方法在触发user_logged_in信号前,已经完成了会话持久化,重复调用保存会触发会话更新竞态,当数据库中对应的会话记录已被删除时,就会抛出UpdateError。 - 旧会话删除逻辑存在误删风险:同一账号二次登录时,如果当前请求的会话已经关联了该用户(比如用户未退出直接提交登录请求),删除操作会把当前正在使用的会话记录也删掉,后续会话操作直接报错。
- 测试方法误区:如果使用同一浏览器测试多用户登录,由于浏览器共享Session Cookie,第二个用户登录时Django会自动销毁第一个用户的服务端会话,关联的
UserSession会因级联删除被清理,表现为表中始终只有一条记录,换不同浏览器测试即可复现多用户会话存储效果。 - 缺少会话校验逻辑:原有代码仅在登录时做会话清理,没有全局校验逻辑,当
UserSession记录异常丢失时,限制逻辑自然失效。
修复方案
1. 修正信号接收器逻辑
from django.contrib.auth import user_logged_in from django.dispatch.dispatcher import receiver from django.contrib.sessions.models import Session from myapp.models import UserSession @receiver(user_logged_in) def remove_other_sessions(sender, user, request, **kwargs): # 先拿到当前已生成的有效会话key(login流程已保存过会话,无需重复save) current_session_key = request.session.session_key # 删除该用户除当前会话外的所有旧会话 Session.objects.filter(usersession__user_acc=user).exclude(pk=current_session_key).delete() # 关联当前会话和用户 UserSession.objects.update_or_create( user_acc=user, defaults={"session_manager": Session.objects.get(pk=current_session_key)} )
注意:信号代码不要直接放在views.py中,建议单独新建signals.py存放,然后在对应应用的apps.py的ready()方法中导入信号,确保应用启动时信号被正确注册:
# myapp/apps.py from django.apps import AppConfig class MyAppConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'myapp' def ready(self): import myapp.signals
同时要确保settings.py中注册的应用是使用自定义的AppConfig,而不是默认的应用名。
2. 新增全局会话校验中间件(可选但推荐)
新增中间件确保即使会话记录异常,也能强制踢出非活跃会话:
# myapp/middleware.py from django.shortcuts import redirect from django.contrib.auth import logout from myapp.models import UserSession class SingleSessionCheckMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): if request.user.is_authenticated: # 校验当前会话是否是该用户的有效关联会话 if not UserSession.objects.filter( user_acc=request.user, session_manager__pk=request.session.session_key ).exists(): logout(request) return redirect('/login/') response = self.get_response(request) return response
在settings.py的MIDDLEWARE列表中添加该中间件,注意要放在SessionMiddleware和AuthenticationMiddleware之后:
MIDDLEWARE = [ # ... 其他默认中间件 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', # 新增的单会话校验中间件 'myapp.middleware.SingleSessionCheckMiddleware', ]
修复后即可实现预期效果:同一账号二次登录时,旧会话会被自动清理,旧用户访问时会被强制下线,多用户在不同设备登录时各自的会话关联记录会独立存储互不影响。
内容的提问来源于stack exchange,提问作者Al G
相关产品推荐
相关产品推荐

