You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.09 03:42:01