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

如何在Django中实现用户单端登录限制:新登录触发旧端自动登出?

嘿,这个需求我之前在项目里落地过,核心思路就是跟踪用户的有效会话,每次新登录时 invalidate 旧会话。下面给你一步步拆解具体实现方案,都是基于Django原生机制,不用额外第三方库:

核心思路

Django默认用django_session表存储用户会话,每个会话对应唯一的session_key。我们要给每个用户绑定当前有效的会话key,当用户新登录时,找到旧会话并标记为过期,同时更新用户的当前会话key。

具体实现步骤

1. 扩展用户模型,添加会话跟踪字段

首先需要给用户模型加一个字段存储当前有效的会话key。如果用默认的User模型,推荐继承AbstractUser扩展:

# your_app/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    # 存储当前有效的会话key,允许为空(用户未登录时)
    current_session_key = models.CharField(max_length=40, null=True, blank=True)

记得在settings.py里指定自定义用户模型:

AUTH_USER_MODEL = 'your_app.CustomUser'

然后执行迁移命令:

python manage.py makemigrations
python manage.py migrate

2. 用信号处理登录事件,自动失效旧会话

Django提供了user_logged_in信号,我们可以监听这个信号,在用户登录成功后处理旧会话:

# your_app/signals.py
from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.contrib.sessions.models import Session
from django.dispatch import receiver
from django.utils import timezone

@receiver(user_logged_in)
def invalidate_old_session_on_login(sender, request, user, **kwargs):
    # 获取用户之前的有效会话key
    old_session_key = user.current_session_key
    if old_session_key:
        try:
            # 找到旧会话并设置为已过期
            old_session = Session.objects.get(session_key=old_session_key)
            old_session.expire_date = timezone.now()
            old_session.save()
        except Session.DoesNotExist:
            # 旧会话已经过期或被删除,无需处理
            pass
    
    # 更新用户当前的会话key为新登录的会话
    user.current_session_key = request.session.session_key
    user.save()

@receiver(user_logged_out)
def clear_session_key_on_logout(sender, request, user, **kwargs):
    # 用户主动登出时,清空会话key记录
    if user.is_authenticated:
        user.current_session_key = None
        user.save()

需要在apps.py里注册信号,确保Django启动时加载:

# your_app/apps.py
from django.apps import AppConfig

class YourAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'your_app'

    def ready(self):
        # 导入信号模块
        import your_app.signals

3. 前端处理会话失效后的跳转

当旧会话被标记为过期后,用户在旧标签页/设备操作时,Django会返回未登录状态。为了提升用户体验,可以在前端定时检查登录状态,发现失效后自动跳转登录页:

第一步:写一个检查登录状态的API视图

# your_app/views.py
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required

@login_required
def check_authentication(request):
    # 只要用户登录,就返回True
    return JsonResponse({'is_authenticated': True})

在urls.py里配置路由:

# your_app/urls.py
from django.urls import path
from . import views

urlpatterns = [
    # ... 其他路由
    path('api/check-auth/', views.check_authentication, name='check_auth'),
]

第二步:在前端模板添加检测逻辑

在你的base模板(所有页面都继承的模板)里加入这段JS:

<script>
function checkAuthStatus() {
    fetch('{% url "check_auth" %}')
        .then(response => {
            if (!response.ok) {
                // 非200状态码,说明未登录
                window.location.href = '{% url "login" %}';
            }
        })
        .catch(error => {
            console.error('检查登录状态失败:', error);
        });
}

// 每隔30秒检查一次,可根据需求调整间隔
setInterval(checkAuthStatus, 30000);

// 页面加载时也检查一次
window.addEventListener('load', checkAuthStatus);
</script>
额外优化点
  • 同一浏览器标签页的限制:默认情况下,同一浏览器的不同标签页共享会话,所以用户在同一浏览器开新标签页登录不会触发旧会话失效。如果需要限制到标签页级别,得结合前端的localStorage或sessionStorage生成唯一标识,每次请求携带这个标识,后端验证标识是否与用户绑定的一致——不过这种方案复杂度较高,通常单端登录需求不需要这么细粒度。
  • 会话安全性:在settings.py里开启SESSION_COOKIE_SECURE = True(生产环境用HTTPS时),SESSION_COOKIE_HTTPONLY = True,防止会话被XSS攻击窃取。
  • 处理会话过期:如果用户的会话自然过期(超过SESSION_COOKIE_AGE设置的时间),current_session_key可能还存在,但对应的会话已经被Django清理,不过我们的信号里已经用try-except处理了这种情况,不会报错。

内容的提问来源于stack exchange,提问作者Dhruv Singhal

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 00:07:41