issue-single-device-login.md 3.5 KB

用户/骑手/商家 App 单设备登录

来源:需求讨论(参照 cte_server 已有实现),2026-09-22 状态:后端已实现 记录人:开发

需求

用户、骑手、商家三个 App,一个账号只能在一台设备登录;新设备登录后旧设备被踢下线。

cte_server 参考实现(两个半环)

  1. 登录删旧会话:三端登录时 deleteKeys(tokenKey + userId + ":*");商家按 User-Agent 分 APP/PC 两个 key,只踢 APP(PC 独立并存,即商家 = 1 App + 1 PC)。
  2. 鉴权强制验会话AuthAspect):JWT 验签后,jti 含冒号(新格式会话 token)→ Redis 必须存在该键,否则 401;无冒号的历史 token 跳过(兼容)。

foodie-server 改造前现状

  • 登录删旧会话:已齐全(与 cte 同构代码):shanglodeing(APP 踢 APP/PC 独立)、syslodeing 踢 QS、lodeing 踢 USER、oauthLogin/LineCallback/注册路径均踢。
  • 鉴权侧:AuthAspect 被改造为 @Auth(session=true) 才校验,且仅对商家账号(userType 1/3/4/5)拒绝;用户(0)/骑手(2) 显式豁免——登录虽删了旧会话,但旧设备 token(15 天内)在所有接口照常放行,单设备实际不生效(半成品)。

本次改动

文件 改动
Auth.java 删除 session() 属性(全局校验后无存在意义;8 个商家控制器 40 处 @Auth(session = true) 同步改为 @Auth
AuthAspect.java 改为 cte 同款全局规则:jti 含冒号 → hasKey(jti) 必须为真否则返回 401(与 token 过期同响应,App 走既有登出路径);无冒号 → 放行。删除 isConsumerUser 豁免及每次请求的用户表查询
MerchantSessionAuthAnnotationTest 重写:商家端点 @Auth 声明断言(不含 session)+ 切面行为用例(会话被删 → 401、会话存活 → 放行并写入可信身份、无冒号旧 token → 放行且不查 Redis)

商家行为对账:改前 session=true 路径就是 hasKey 校验,改后语义一致;变化仅在用户/骑手从"豁免"变"校验",即本次需求本身。

风险评估(已逐项核对)

  1. 存量 token 兼容:全部 7 处 token 签发都走写会话的 JwtUtil.setToken(tokenKey, dto),15 天内的 token 会话都在 Redis,开启校验不掉线;无冒号历史 token 走兼容分支放行。
  2. App 端无需升级:被踢返回与 token 过期相同的 401 响应,App 走既有处理。
  3. 商家被踢体验变化:改前商家会话失效抛 ServiceException(500 语义),改后统一 401——更一致,商家 App 按 401 重新登录。
  4. 共享账号互顶:单设备需求本身预期内。
  5. 推送 cid:登录覆盖 cid,单设备后与设备一一对应,推送不再串设备(改善)。
  6. 029 设备信任:DeviceTrustService 信任记录与会话删除独立,无冲突。
  7. 管理后台:ruoyi-framework 独立 TokenService,不受影响。
  8. Redis 故障:会话丢失 → 全端重登(cte 线上同款风险,可接受)。
  9. 骑手在线标识:被踢旧设备的 offline 标志保持到下次登录,推送 cid 已切到新设备,接单不受影响。

验证

  • MerchantSessionAuthAnnotationTest:注解声明 + 切面行为(401/放行/可信身份写入)全绿
  • 全量回归:除既有已知红测试 createRejectsUnsupportedPayType 外全部通过

待办

  • 观察上线后 401 量与登录量变化,确认无异常掉线投诉