101 lines
6.2 KiB
Markdown
101 lines
6.2 KiB
Markdown
# IM 登录失败锁定 - 移动端对接文档
|
||
|
||
> 功能:用户登录密码连续输错达到配置阈值后,账号暂时锁定;锁定期间登录请求被服务端直接拒绝。本文档面向原生移动端(Android / iOS,基于 Tinode SDK)对接者。
|
||
> 版本:v1(2026-09-20) | 服务端版本:im_server(已发布至 dev)
|
||
|
||
---
|
||
|
||
## 1. 功能交互总览
|
||
|
||
```
|
||
移动端登录(basic 认证,协议不变)
|
||
├─ 密码正确 → 计数清零,登录成功(行为与现状完全一致)
|
||
├─ 密码错误(未达阈值)→ 401 authentication failed(现状不变)
|
||
├─ 密码错误达到阈值 → 429 账号锁定(message 含剩余时间)
|
||
└─ 锁定期间再次登录(无论密码对错)→ 429,服务端不校验密码直接拒绝
|
||
```
|
||
|
||
- **登录协议与请求结构零变更**:锁定逻辑全部在服务端(im_server basic 认证链路),移动端只做错误展示适配。
|
||
- **阈值/时长由管理后台配置**:【安全管理→安全策略】的登录限频策略,按租户下发(不同租户配置可能不同),客户端不可配置、无需缓存。
|
||
- **解锁途径**:① 锁定时间自然到期自动解锁;② 用户联系客服/管理员,在管理后台【日志审计→登录失败日志】手动解锁。两种途径均无需客户端参与。
|
||
|
||
## 2. 错误响应契约(核心对接点)
|
||
|
||
### 2.1 响应结构(WebSocket 传输)
|
||
|
||
登录 `{login}` 消息的 ctrl 响应:
|
||
|
||
| 场景 | code | text | params |
|
||
|---|---|---|---|
|
||
| 密码错误,未达阈值 | 401 | `authentication failed` | — |
|
||
| 密码错误达到阈值,触发锁定 | **429** | `登录错误次数过多,账号已锁定,请 N 后再试`(N 按剩余时长显示:不足 1 分钟为「1 分钟以内」,不足 1 小时为「1 小时以内」,不足 1 天为「N 小时」,其他为「N 天」) | `{"what":"auth","retry_after_seconds":60}` |
|
||
| 锁定期间再次登录 | **429** | 同上(N 随剩余时间递减) | 同上 |
|
||
| 登录成功 | 200 | `ok` | 正常 auth 信息 |
|
||
|
||
实测样例(连错 2 次触发锁定,阈值为 2 的租户):
|
||
|
||
```json
|
||
// 第 1 次错误密码
|
||
{"ctrl": {"id": "t1", "code": 401, "text": "authentication failed", "ts": "..."}}
|
||
|
||
// 第 2 次错误密码(达到阈值)
|
||
{"ctrl": {"id": "t1", "code": 429,
|
||
"text": "登录错误次数过多,账号已锁定,请 1 分钟以内再试",
|
||
"params": {"what": "auth", "retry_after_seconds": 60},
|
||
"ts": "..."}}
|
||
|
||
// 锁定期间(约 12 秒后)正确密码登录,同样被拒绝且剩余时间递减
|
||
{"ctrl": {"id": "t1", "code": 429,
|
||
"text": "登录错误次数过多,账号已锁定,请 1 分钟以内再试",
|
||
"params": {"what": "auth", "retry_after_seconds": 48},
|
||
"ts": "..."}}
|
||
```
|
||
|
||
### 2.2 字段说明
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
|---|---|---|
|
||
| `code` | int | HTTP 语义状态码。**429 = 账号锁定**,与 401(密码错误)区分 |
|
||
| `text` | string | **面向用户展示的完整中文文案,剩余时长自动格式化:不足 1 分钟显示「1 分钟以内」,不足 1 小时显示「1 小时以内」,不足 1 天按小时(向下取整),其他按天(向下取整)**。客户端直接展示即可,无需自行拼接时间 |
|
||
| `params.retry_after_seconds` | int | 剩余锁定秒数(向上取整),可用于 UI 倒计时/禁用登录按钮 |
|
||
| `params.what` | string | 固定 `auth`,表示错误发生在认证环节 |
|
||
|
||
### 2.3 gRPC 传输(Android SDK 默认)
|
||
|
||
gRPC 通道下服务端返回相同的 ctrl 结构(code/text/params 逐字段保留),SDK 会将其封装为带 code 的登录异常抛出。**识别方式不变:code == 429**。
|
||
|
||
## 3. 客户端处理建议
|
||
|
||
1. **错误识别**:登录失败回调中判断 `code == 429`(或 SDK 异常携带的 ctrl code)。仅 `429` 按锁定处理,`401`/`403` 维持现有文案与逻辑。
|
||
2. **文案展示**:
|
||
- 优先展示服务端 `text`(已含剩余分钟数,中文);
|
||
- `text` 为空时的兜底文案:`登录错误次数过多,请稍后再试`。
|
||
3. **禁止静默重试**:锁定期间重试不会带来额外后果(不刷新锁定时间、不计新失败),但每次都得到 429 提示,建议拿到 429 后**禁用登录按钮或展示倒计时**(用 `retry_after_seconds`),引导用户等待或走找回密码。
|
||
4. **倒计时精度**:`retry_after_seconds` 为剩余秒数(比真实剩余时间多算 1 秒余量),客户端倒计时结束即可恢复登录,无需与服务端时钟同步。
|
||
5. **多端一致**:Web 端(im_webapp)已按本契约实现(`errors.ts` 的 429 分支),移动端保持相同交互语义。
|
||
|
||
## 4. 服务端行为细节(影响测试预期)
|
||
|
||
| 行为 | 说明 |
|
||
|---|---|
|
||
| 计数对象 | 按**账号名**(username,不区分大小写)哈希计数,与设备/IP 无关:换设备登录同一账号同样累计 |
|
||
| 计数窗口 | 滑动窗口 `window_seconds`(默认 30 分钟):窗口内累计达到阈值即锁定;窗口过期后重新计数 |
|
||
| 锁定时长 | `lock_seconds`(默认 15 分钟),从触发锁定的那次失败请求起算 |
|
||
| 锁定期内正确密码 | 仍返回 429(校验前置于密码比对),**且不刷新锁定时间**(剩余秒数持续递减) |
|
||
| 锁定期内请求 | 不写入失败审计日志(避免日志膨胀) |
|
||
| 成功登录 | 计数与锁定记录立即清零 |
|
||
| 仅 basic 认证 | 锁定只作用于 `basic` scheme(账号密码)登录;token 重连不受影响 |
|
||
| 策略关闭 | 租户未配置或 `enabled=false` 时,行为与旧版完全一致(无 429) |
|
||
|
||
## 5. 自测要点
|
||
|
||
| 用例 | 预期 |
|
||
|---|---|
|
||
| 错误密码 × (阈值-1) 次 | 每次 401,文案「账号或密码错误」 |
|
||
| 错误密码第 N 次(达阈值) | 429 + 锁定文案 + `retry_after_seconds` ≈ lock_seconds |
|
||
| 锁定期内用**正确**密码 | 仍 429,`retry_after_seconds` 递减 |
|
||
| 等待锁定到期(或联系管理员解锁) | 正确密码登录成功 200 |
|
||
| 登录成功后再连错 (阈值-1) 次 | 均 401(成功登录已清零历史计数) |
|
||
|
||
> 注:测试环境的阈值/时长以【安全管理→安全策略】当前发布版本为准,联调时可临时调小(如 2 次/60 秒)加速验证。
|