Hello,大家好,我是 Sunday。
昨天,有位同学在面试的过程中被问到了一个很有意思的问题:【如何限制一个账号只能在一处登录?】,既:单设备登录。
PS
:很多同学会把 单设备登录 和 单点登录 搞混,但是他们之间本质上是不一样的。
- 单设备登录:同一账号 在同一时间只能在一个设备上登录,如果用户在另一台设备上登录,则之前的设备会被自动下线。
- 单点登录(SSO):用户在多个系统或应用之间只需要登录一次,就可以访问所有相关系统,无需再次输入凭据。
言归正传,咱们回到当前面试题本身 如何限制一个账号只能在一处登录?
技术方案
要实现 单设备登录,我们需要设立一种机制,确保同一账号在不同设备上不能同时保持活跃。
常见的实现方式仅从前端角度来说的话,主要有两种:
1:基于 Token 版本控制(推荐)
核心思想:Token 版本号(
token_version
)充当唯一凭证,每次新设备登录时,版本号+1
,旧 Token 失效。
我们来拆解下这个流程:
首先第一步: 用户首次登录
- 用户登录后,数据库中的
token_version
设为1
。 - 后端生成 JWT Token,并在
payload
中加入{ userId: 1, tokenVersion: 1 }
。 - 返回 Token 给前端,前端存储在
localStorage
或Authorization
头中。
然后第二步: 用户在新设备登录
- 新设备登录后,后端查询该用户的
token_version
,然后 +1,更新到数据库(如token_version=2
)。 - 生成新的 Token
{ userId: 1, tokenVersion: 2 }
,返回给新设备。
第三步: 旧设备携带 Token 访问接口
- 旧设备的 Token 仍然是
{ userId: 1, tokenVersion: 1 }
,但数据库中token_version=2
。 - 服务器验证 Token,发现
tokenVersion !== 数据库的 token_version
,返回401
,拒绝访问。
第四步: 旧设备强制下线
- 旧设备前端收到
401
响应后:- 清除本地 Token
- 跳转到登录页
- 提示 "账号已在其他设备登录,请重新登录"
以上方案是实现 单设备登录 最简单的方案,核心就是维护了 token_version
的版本值
2:基于 WebSocket 的实时强制下线
核心思想:每次用户在新设备登录时,服务器通过 WebSocket 通知旧设备下线,旧设备收到消息后清除 Token 并强制登出。
咱们通常拆解下这个流程:
首先第一步: 用户首次登录
- 用户登录后,服务器建立 WebSocket 连接,并在 Redis 或者 数据库中存储对应状态,如:
{ userId: 1, socketId: "xyz123" }
以记录该设备的 WebSocket 连接 ID。
然后第二步:用户在新设备登录
- 新设备登录后,服务器检测到该用户已有活跃连接。
- 给旧设备发送 WebSocket 消息:
"你的账号已在其他设备登录,请重新登录"
- 删除旧设备的 WebSocket 连接记录
- 新设备建立 WebSocket 连接,存储
{ userId: 1, socketId: "abc456" }
。
第三步:旧设备收到 WebSocket 消息
- 旧设备监听到 "被踢下线" 消息:
- 清除本地 Token
- 自动跳转到登录页
- 提示 "账号已在其他设备登录"
与上一个相比,这里核心就是利用了 WebSocket 双向通讯
的能力来实现 强制下线 的功能,好处是可以做到 即时响应。麻烦的地方是需要重新对接 ws
协议。
我们可以通过以下表格来对比下两种方案的差异:
方案 | 优势 | 劣势 |
---|---|---|
基于 Token 版本控制 | 逻辑简单,后端维护 Token 版本即可,兼容性好 | 需要前端主动处理 401 ,可能有短暂的延迟 |
基于 WebSocket 实时强制下线 | 即时性强,用户体验更好 | 需要 WebSocket 连接,维护连接状态,断连时需要额外处理 |
当然,这两种方案也可以结合起来进行使用:
以 WebSocket 优先,当 WebSocket 连接异常时,后端回退到 Token 版本校验。这样既能 保证即时强制下线,也能 兼容不支持 WebSocket 的场景。
前端训练营:1v1 私教,9 大服务,终身辅导,帮你拿到满意的
offer
。 已帮助数百位同学拿到了中大厂offer