如何限制Django用户同一时间仅能在单设备/标签页/窗口使用应用
问题解答
疑问1:其他会生成新session_key的场景
- 用户主动登出后重新登录
- 服务端存储的session过期后,用户再次发起访问
- 代码中主动调用
request.session.flush()或request.session.cycle_key()方法 - 同一设备使用不同浏览器、或同一浏览器开启隐身窗口访问应用
- 服务端手动清除了session存储中对应用户的session记录
疑问2:浏览器携带Authorization头测试的方法
你可以用以下三种常用方案实现:
- 使用浏览器控制台发请求:打开目标网站的开发者工具-控制台,直接执行fetch请求,手动携带认证头,示例代码如下:
fetch('你的接口访问地址', { headers: { 'Authorization': 'Bearer 替换为你的实际令牌值' } })
- 使用请求头修改插件:安装ModHeader之类的浏览器扩展,在插件中配置全局的Authorization头,配置完成后正常访问接口就会自动携带配置的头信息。
- 令牌写入Cookie:如果你的服务端支持从Cookie读取令牌,可以直接将令牌写入当前站点的Cookie,访问时会自动携带。
疑问3:信号未触发的原因
是的,该问题和你使用Postman走令牌认证直接相关。Django自带的user_logged_in、user_logged_out信号只有在调用Django默认的auth.login()、auth.logout()方法时才会触发。令牌认证属于无状态认证,全程不会调用这两个方法,因此信号自然不会触发,和你是否正确导入信号无关。
现有代码问题修复建议
你当前的实现还存在两个逻辑问题:
- 中间件的校验逻辑放在了
get_response(request)之后,此时请求已经走完了视图逻辑,就算校验到重复登录也已经执行了核心业务逻辑,应该把校验逻辑移到调用get_response之前。 - 令牌认证场景默认不使用Django的session体系,继续用session_key实现单设备登录会出现逻辑不兼容的问题,你可以改用存储有效令牌的方案实现:
1. 修改模型
# models.py class LoggedInUser(models.Model): user = models.OneToOneField(User, related_name='logged_in_user', on_delete =models.CASCADE, null=True, blank=True) active_token = models.CharField(max_length=255, null=True, blank=True)
2. 重写认证类(以DRF TokenAuthentication为例)
from rest_framework.authentication import TokenAuthentication from rest_framework.exceptions import AuthenticationFailed class SingleDeviceTokenAuthentication(TokenAuthentication): def authenticate_credentials(self, key): user, token = super().authenticate_credentials(key) logged_in_user, _ = LoggedInUser.objects.get_or_create(user=user) if logged_in_user.active_token and logged_in_user.active_token != key: raise AuthenticationFailed("你已有活跃会话,请先在其他设备登出") if not logged_in_user.active_token: logged_in_user.active_token = key logged_in_user.save() return (user, token)
3. 补充登出逻辑
在登出接口中,将对应用户的active_token字段置空或者直接删除LoggedInUser记录即可。
内容的提问来源于stack exchange,提问作者Dhruv Singhal
相关产品推荐
相关产品推荐

