4.2 KiB
feat(user): SMS 登录限定制师 + active role 自动切 CUSTOMIZER
仓库: HL (后端 hl-user-service) 关联 PR/Issue: PR #2920, Closes #2918 日期: 2026-05-22 影响范围: 管理端
POST /admin/auth/sms/loginSMS 验证码登录入口 接收方: mmg (前端) 前端: 登录页前端无 API 改动, 但 SMS 登录成功后data.role一定是 CUSTOMIZER (即使该 admin 还有其他角色)
🚨 关键变化
SMS 验证码登录入口 (POST /admin/auth/sms/login) 现在只允许有 CUSTOMIZER 角色的 admin 登录, 且登录成功后强制以定制师视角颁发 token (即使 admin 还有 ADMIN/OPERATOR 等其他角色)。
业务原因: SMS 登录场景仅定制师小程序分享需要 (#2907), 其他角色不应该用 SMS 绕过 2FA 登入后台。
一、API 影响
POST /admin/auth/sms/login 行为变化
| 场景 | 之前 | 现在 |
|---|---|---|
| 仅 CUSTOMIZER 角色 admin 用对码登录 | 颁发 token, role = admin.currentRoleId 对应的 roleKey |
颁发 token, role 强制 = CUSTOMIZER |
| 多角色 admin (如 CUSTOMIZER+ADMIN) 用对码登录 | 颁发 token, role = admin.currentRoleId 对应的 roleKey |
颁发 token, role 强制 = CUSTOMIZER (无视 currentRoleId) |
| 无 CUSTOMIZER 角色 admin 用对码登录 | 颁发 token, role = 当前 active role |
返 200102 INVALID 验证码错误,剩余 N 次尝试 + fail+1 (防枚举) |
| mobile 不绑 admin 用对码登录 | 200102 INVALID (现有防枚举路径) | 200102 INVALID (无变化) |
防枚举设计
"无 CUSTOMIZER 角色 admin" 路径返回的 code/message 跟 "mobile 不绑 admin" 路径完全一致 (200102 INVALID + remainingAttempts),攻击者无法区分这两种情况, 也无法用 SMS 入口枚举哪些 mobile 是非定制师 admin。
fail count 也会累加, 错 5 次仍触发 ADMIN_SMS_CODE_VERIFY_LOCKED 15 分钟锁定 (复用 #2889 锁定机制)。
active role 强制 CUSTOMIZER 后
- 颁发的 access token 内嵌的
role字段 =CUSTOMIZER - 后续基于 token 的
@PreAuthorize("hasRole('CUSTOMIZER')")等权限校验都按定制师视角 - 不污染
admin.currentRoleId持久化: SMS 是临时切角色, 不影响下次密码登录时的默认角色
🚨 前端需要做的事
1. 登录后角色显示
SMS 登录成功后 data.role 一定是 CUSTOMIZER, 顶部用户信息组件 / 角色 badge 应该按定制师展示。
不需要前端额外区分 SMS 登录 vs 账密登录: 后端已经把 role 强制设为 CUSTOMIZER, 前端按响应字段渲染即可。
2. 多角色切换
如果未来前端有"切换角色"功能 (PUT /admin/auth/role), SMS 登录拿到的 CUSTOMIZER token, 切到其他角色行为等同于密码登录路径 (后端按现有逻辑处理)。
3. 错码无新增
200102 错码已经存在, 前端无需新增映射。200107 是后端审计码不会返前端, 前端忽略。
4. 自查
grep -rE "/admin/auth/sms/login|role.*CUSTOMIZER" hl-ui/src
确认登录后 role 字段处理逻辑能正确显示 CUSTOMIZER。
二、新错码 (后端审计用, 不返前端)
| code | 含义 |
|---|---|
| 200107 | ADMIN_SMS_LOGIN_NOT_CUSTOMIZER 短信验证码登录仅限定制师 (仅服务端 log.warn 审计, 防枚举不返前端) |
三、测试服验证记录
部署到测试服 api.test.1814.love:9443 后真测 (2026-05-22 19:05):
| # | 场景 | 实测 |
|---|---|---|
| T1 | 仅 CUSTOMIZER admin 登录 | ✅ role=CUSTOMIZER + roleName=定制师 + token + requireTwoFa=False |
| T2 | 仅 ADMIN admin (无 CUSTOMIZER) 登录 | ✅ 200102 INVALID + remainingAttempts=4 (防枚举) |
| T3 | 多角色 admin (ADMIN+CUSTOMIZER, currentRoleId=ADMIN) | ✅ 强制返 role=CUSTOMIZER, DB current_role_id=2 保持不污染 |
四、不在本期范围
- 账号密码登录路径
/admin/auth/login行为完全不动 (仍按admin.currentRoleId) - 让非定制师能用 SMS 登录 (明确仅定制师)
- 持久化改 admin.currentRoleId (SMS 是临时切角色)
五、联系人
后端: wx (呼籁旅行) 前端: mmg
如有疑问可在 #2918 评论区留言。