feat(server-rs): 接入真实短信验证码链路
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
# Axum 手机验证码真实短信 Provider 接入设计
|
||||
|
||||
日期:`2026-04-22`
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于冻结 Rust `api-server + module-auth + platform-auth` 切换到真实短信链路时的最小可编码边界,解决当前 `module-auth` 仍固定使用 mock 验证码 `123456`,导致 `server-rs` 无法接入真实短信发送与真实验证码校验的问题。
|
||||
|
||||
## 2. 当前问题
|
||||
|
||||
截至 `2026-04-22`,Rust 侧手机号登录存在以下状态:
|
||||
|
||||
1. `POST /api/auth/phone/send-code` 已存在,但 `module-auth` 内部仍写死 `123456`。
|
||||
2. `POST /api/auth/phone/login` 校验的是本地内存快照里的固定验证码,不是真实短信平台生成的验证码。
|
||||
3. 即使把发送动作切到真实阿里云短信,如果校验仍留在本地 mock,整条登录链仍然不可用。
|
||||
|
||||
## 3. 本次目标
|
||||
|
||||
本次必须达成:
|
||||
|
||||
1. Rust 侧短信 provider 支持 `mock` 与 `aliyun` 两种模式。
|
||||
2. `send-code` 在 `aliyun` 模式下调用阿里云 `SendSmsVerifyCode`。
|
||||
3. `phone/login` 与 `wechat/bind-phone` 在 `aliyun` 模式下调用阿里云 `CheckSmsVerifyCode`。
|
||||
4. `module-auth` 不再保存验证码明文,只保存发送冷却、有效期和失败次数所需的最小快照。
|
||||
5. `shared-contracts` 公开响应 contract 维持不变,仍只返回:
|
||||
- `ok`
|
||||
- `cooldownSeconds`
|
||||
- `expiresInSeconds`
|
||||
- `providerRequestId`
|
||||
|
||||
## 4. crate 边界
|
||||
|
||||
### 4.1 `platform-auth`
|
||||
|
||||
负责:
|
||||
|
||||
1. 短信 provider 配置结构。
|
||||
2. `mock / aliyun` provider 实现。
|
||||
3. 阿里云 RPC 请求签名、发送与校验。
|
||||
4. provider 级错误归一化。
|
||||
|
||||
### 4.2 `module-auth`
|
||||
|
||||
负责:
|
||||
|
||||
1. 手机号归一化。
|
||||
2. 发送冷却与验证码快照 TTL。
|
||||
3. 校验失败次数累加与耗尽删除。
|
||||
4. 手机号用户创建、复用、微信补绑归并。
|
||||
|
||||
### 4.3 `api-server`
|
||||
|
||||
负责:
|
||||
|
||||
1. 从环境变量读取短信 provider 配置。
|
||||
2. 构建 `SmsAuthProvider` 并注入 `PhoneAuthService`。
|
||||
3. 把领域错误映射成 HTTP 错误。
|
||||
|
||||
## 5. 配置设计
|
||||
|
||||
新增或继续使用以下环境变量:
|
||||
|
||||
1. `SMS_AUTH_ENABLED`
|
||||
2. `SMS_AUTH_PROVIDER`
|
||||
- `mock`
|
||||
- `aliyun`
|
||||
3. `ALIYUN_SMS_ENDPOINT`
|
||||
- 默认 `dypnsapi.aliyuncs.com`
|
||||
4. `ALIYUN_SMS_ACCESS_KEY_ID`
|
||||
5. `ALIYUN_SMS_ACCESS_KEY_SECRET`
|
||||
6. `ALIYUN_SMS_SIGN_NAME`
|
||||
7. `ALIYUN_SMS_TEMPLATE_CODE`
|
||||
8. `ALIYUN_SMS_TEMPLATE_PARAM_KEY`
|
||||
- 默认 `code`
|
||||
9. `ALIYUN_SMS_COUNTRY_CODE`
|
||||
- 默认 `86`
|
||||
10. `ALIYUN_SMS_SCHEME_NAME`
|
||||
11. `ALIYUN_SMS_CODE_LENGTH`
|
||||
- 默认 `6`
|
||||
12. `ALIYUN_SMS_CODE_TYPE`
|
||||
- 默认 `1`
|
||||
13. `ALIYUN_SMS_VALID_TIME_SECONDS`
|
||||
- 默认 `300`
|
||||
14. `ALIYUN_SMS_INTERVAL_SECONDS`
|
||||
- 默认 `60`
|
||||
15. `ALIYUN_SMS_DUPLICATE_POLICY`
|
||||
- 默认 `1`
|
||||
16. `ALIYUN_SMS_CASE_AUTH_POLICY`
|
||||
- 默认 `1`
|
||||
17. `ALIYUN_SMS_RETURN_VERIFY_CODE`
|
||||
- 默认 `false`
|
||||
18. `SMS_AUTH_MOCK_VERIFY_CODE`
|
||||
- 默认 `123456`
|
||||
|
||||
## 6. provider 行为
|
||||
|
||||
### 6.1 `mock`
|
||||
|
||||
1. 发送验证码时不访问外部网络。
|
||||
2. 返回固定 `mock-request-id`。
|
||||
3. 校验时使用内存中的 mock 验证码。
|
||||
|
||||
### 6.2 `aliyun`
|
||||
|
||||
1. 发送验证码调用 `SendSmsVerifyCode`。
|
||||
2. 校验验证码调用 `CheckSmsVerifyCode`。
|
||||
3. 使用阿里云 RPC 签名口径:
|
||||
- `SignatureMethod=HMAC-SHA1`
|
||||
- `SignatureVersion=1.0`
|
||||
4. 当前仍只支持中国大陆手机号。
|
||||
|
||||
## 7. 状态与快照
|
||||
|
||||
`module-auth` 内部验证码快照保留:
|
||||
|
||||
1. `phone_number`
|
||||
2. `scene`
|
||||
3. `expires_at`
|
||||
4. `last_sent_at`
|
||||
5. `failed_attempts`
|
||||
|
||||
明确不再保留:
|
||||
|
||||
1. 验证码明文
|
||||
2. 验证码 hash
|
||||
|
||||
校验流程改为:
|
||||
|
||||
1. 先检查是否存在活跃快照。
|
||||
2. 再检查是否过期。
|
||||
3. 再调用 provider 做真实验证码校验。
|
||||
4. 校验失败时累加失败次数。
|
||||
5. 达到上限时删除快照并返回 `429`。
|
||||
6. 校验成功后删除快照。
|
||||
|
||||
## 8. 错误语义
|
||||
|
||||
1. 手机号格式错误:`400`
|
||||
2. 验证码格式错误:`400`
|
||||
3. 验证码不存在或已过期:`400`
|
||||
4. 校验失败:`400`
|
||||
5. 验证码错误次数耗尽:`429`
|
||||
6. 阿里云配置缺失:`500`
|
||||
7. 阿里云上游失败:`502`
|
||||
|
||||
## 9. 测试要求
|
||||
|
||||
至少覆盖:
|
||||
|
||||
1. `mock` provider 的发送与登录仍可跑通。
|
||||
2. `aliyun` provider 缺配置时会在服务初始化阶段报错。
|
||||
3. 发送冷却逻辑不依赖验证码明文仍然有效。
|
||||
4. 校验失败次数耗尽后会删除快照。
|
||||
5. `send-code` 成功时仍返回既有 contract。
|
||||
|
||||
## 10. 非目标
|
||||
|
||||
本次明确不做:
|
||||
|
||||
1. 短信送达回执接口
|
||||
2. `sms_auth_event` 真实持久化
|
||||
3. 图形验证码
|
||||
4. 更细粒度的 provider 错误码透传 DTO
|
||||
@@ -19,6 +19,7 @@
|
||||
- [AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md](./AUTH_SESSIONS_QUERY_DESIGN_2026-04-21.md):`/api/auth/sessions` 会话列表设计,冻结当前设备识别、多端登录字段映射、`clientLabel` 兼容策略与 Rust 首版接口边界。
|
||||
- [PHONE_AUTH_AXUM_MINIMAL_FLOW_DESIGN_2026-04-21.md](./PHONE_AUTH_AXUM_MINIMAL_FLOW_DESIGN_2026-04-21.md):手机号验证码登录最小闭环设计,冻结 mock 验证码规则、`send-code` / `phone/login` contract 与 crate 边界。
|
||||
- [PHONE_AUTH_AXUM_RATE_LIMIT_AND_FAILURE_DESIGN_2026-04-21.md](./PHONE_AUTH_AXUM_RATE_LIMIT_AND_FAILURE_DESIGN_2026-04-21.md):手机号验证码冷却与失败次数限制设计,冻结同手机号同场景发送冷却、错误次数耗尽、`429` 与 `Retry-After` contract。
|
||||
- [PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md](./PHONE_AUTH_AXUM_REAL_SMS_PROVIDER_DESIGN_2026-04-22.md)??? Rust `api-server + module-auth + platform-auth` ?????? provider ? crate ???????????/????????????
|
||||
- [WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md](./WECHAT_LOGIN_AXUM_IMPLEMENTATION_DESIGN_2026-04-21.md):Rust `api-server` 微信登录实现设计,冻结微信 provider 接入、系统 JWT 签发边界、`wechat/start` / `wechat/callback` / `wechat/bind-phone` 闭环,以及与后续 `SpacetimeDB` claims 透传的关系。
|
||||
- [WECHAT_LOGIN_REAL_INTEGRATION_RUNBOOK_2026-04-21.md](./WECHAT_LOGIN_REAL_INTEGRATION_RUNBOOK_2026-04-21.md):微信登录从本地 mock 到真实微信开放平台联调的执行手册,覆盖环境变量、回调域名、代理头要求、验证步骤与常见失败排查。
|
||||
- [PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md](./PASSWORD_ENTRY_FLOW_DESIGN_2026-04-21.md):密码登录与自动建号落地设计,冻结 `/api/auth/entry`、幂等兼容策略、模块边界以及与 JWT / refresh cookie 的衔接方式。
|
||||
|
||||
Reference in New Issue
Block a user