
同一个账号,没人改密码,也没人申诉盗号,但用户就是反复掉线。 一查才发现,不是系统崩了,而是两个同类型设备在来回抢登录态。
我第一次接这个需求时,也以为就是配一个“是否允许并发登录”的开关。 但真放到项目里才发现,麻烦根本不在“能不能同时登录”,而在 你怎么定义设备边界、怎么处理互踢、怎么给前端一个能解释清楚的结果。
这篇文章,我就结合 Sa-Token 里的实现,把 同端互斥登录 这件事彻底拆开。
流程演示 Gif,https://img2024.cnblogs.com/blog/2105804/202603/2105804-20260324235519618-1893812181.gif
说实话,如果只是写 Demo,登录策略怎么搞都能跑。 但一旦到了真实生产环境,问题马上就来了。
最常见的几个场景是:
很多团队一开始会把需求理解成两种极端:
但真实业务里,通常不是这么粗暴。 更常见的诉求是:不同端可以共存,同端只能保留一个在线会话。
这就是“同端互斥登录”的价值。
这个概念最容易误解的地方,其实是“互斥”两个字。
它不是说:
它更像是说:
如果你经常用腾讯 QQ,其实对这个体验不会陌生。 它可以手机和电脑同时在线,但一般不会允许你在两台手机上长期共用同一个账号。
💡 这背后不是单纯的技术限制,而是一种很典型的登录策略设计:
这套能力在 Sa-Token 里并不复杂。
核心思路就两步:
isConcurrent 配置为 false。先看最关键的一步。
// 指定`账号id`和`设备类型`进行登录
StpUtil.login(10001, "PC"); 调用这段代码后,如果同一个账号已经在同设备登录过,那么旧会话会被顶下线。 注意,这里影响的是同设备类型,不同设备不受影响。
被顶下线的一方再次访问系统时,会抛出 NotLoginException 异常,场景值=-4。
如果你需要主动把某个设备类型踢下线,可以这样做:
// 指定`账号id`和`设备类型`进行强制注销
StpUtil.logout(10001, "PC"); 这里有个特别容易误伤的点。
如果第二个参数填写 null 或者不填,代表的是 把这个账号所有在线端全部强制注销。
这时,被踢出者再次访问系统时会抛出 NotLoginException 异常,场景值=-2。
另外两个接口在排查问题时也很有用:
// 返回当前token的登录设备类型
StpUtil.getLoginDevice(); // 获取指定loginId指定设备类型端的tokenValue
StpUtil.getTokenValueByLoginId(10001, "APP"); 如果你只是为了跑通功能,到这里其实已经够了。 但如果你真打算把它放进生产,后面的细节才是最容易出事的地方。
这是很多人第一眼的理解。
但 Sa-Token 这套设计,不是简单粗暴地把所有端都干掉,而是把“设备类型”这层维度交给你来定义。
也就是说,真正决定策略边界的,不只是 isConcurrent=false。
还包括你传进去的这个设备标识,到底是不是统一、稳定、可控。
如果你们项目里:
"PC"。"WEB"。那最后的结果大概率就是:
⚠️ 这类问题最烦的地方,不是直接报错,而是它会表现成偶发性登录异常。
很多系统做完互斥登录后,前端收到未登录异常,就统一弹一句“登录已失效,请重新登录”。
这当然能跑。 但用户体验其实很差。
因为 -4 和 -2 的含义并不一样:
-4 更像是 同设备被新登录顶下线。-2 更像是 被显式强制注销。这两种情况,前端提示、日志记录、风控判断,最好别混在一起。 否则用户会觉得系统“莫名其妙掉登录”,而不是知道自己是被互斥策略顶掉了。
这个我在后台系统里见得挺多。
业务方一开始会说: “为了安全,账号不能多人同时登录。”
听起来很合理。 但如果这个账号本身就是共享账号,或者业务流程天然会在多个地点切换登录,那同端互斥做完之后,结果往往不是更安全,而是大家开始互相顶号。
所以这个策略真正适合的是:
如果这些前提不成立,你就要慎用。 不然它解决的不是安全问题,而是制造更多工单。
光把 API 调起来,只能算“功能完成”。 要让这套方案真正稳定,最好再补几层工程兜底。
👇 我自己的经验里,至少要补这 4 件事:
这几件事写起来不复杂。 但少做任何一件,后面排查体验都会明显变差。
很多人会把“同端互斥登录”理解成认证框架里的一个小功能。 我现在更倾向于把它看成 账号体系治理的一部分。
因为它真正回答的是这几个问题:
你会发现,这已经不只是登录代码了。 它背后连着的是:
所以我后来做这类需求,第一步已经不是先写代码了。 而是先把这几个边界跟业务说清楚。
代码只是在执行策略,真正难的是把策略定义对。
如果你准备在项目里落地,可以先按这个顺序理解:
isConcurrent 配置为 false。StpUtil.login(10001, "PC"); 指定设备类型。StpUtil.logout(10001, "PC");。StpUtil.getLoginDevice();。StpUtil.getTokenValueByLoginId(10001, "APP");。这套 API 本身不复杂。 真正拉开项目质量差距的,反而是你有没有把“设备类型”和“异常语义”这两层处理干净。
登录并发不是一个布尔开关,它本质上是账号边界、设备边界和用户体验之间的平衡。
你们项目里,多端登录策略是怎么做的?
相关文章推荐:
如果这篇文章帮到了你,不妨点个分享给同样需要的朋友吧! 你的每一次支持,都是我持续创作的动力!💪