277 lines
9.1 KiB
Markdown
277 lines
9.1 KiB
Markdown
> **For AI assistants**: Read this entire file before writing any code. All decisions here are final. Do not suggest alternatives unless asked.
|
||
|
||
# Fonrey 登录管理技术方案
|
||
|
||
**版本**: 4.0
|
||
**项目**: Fonrey 房产经纪管理系统
|
||
**技术栈**: Django 4.x + HTMX + Alpine.js + PostgreSQL 16 + Redis + Celery
|
||
**关联 PRD**: `PRD/登录管理/用户登录管理模块PRD.md`(v2.0)
|
||
**关联数据模型**: `DATA_MODEL/DATA_MODEL_LOGIN.md`(本方案不重复 DDL)
|
||
**关联契约规范**: `TECH_STACK/API_CONTRACT.md`(全局 API 契约权威)
|
||
**关联测试规范**: `TECH_STACK/测试规范.md`、`TEST_CASES/TEST_CASES_LOGIN_MODULE.md`
|
||
**最后更新**: 2026-04-30
|
||
|
||
---
|
||
|
||
## 变更历史
|
||
|
||
| 日期 | 变更人 | 变更内容 |
|
||
|---|---|---|
|
||
| 2026-04-30 | Atlas | 补充“变更历史”章节(文档治理) |
|
||
|
||
## 一、文档定位与边界
|
||
|
||
本文件定义登录模块的实现口径:
|
||
|
||
1. 模块范围与职责边界
|
||
2. API 端点(页面 / HTMX / JSON)
|
||
3. 安全策略(滑块验证、登录锁定、短信 OTP、会话)
|
||
4. Redis/Celery 运行策略
|
||
5. 错误码与测试映射
|
||
|
||
> 本文件不展开数据表字段与索引。数据结构以 `DATA_MODEL_LOGIN.md` 为唯一权威。
|
||
|
||
---
|
||
|
||
## 二、范围定义(以 PRD v2.0 为准)
|
||
|
||
### 2.1 P0 必须覆盖
|
||
|
||
- Tenant Code 识别(首次启动 + 切换公司)
|
||
- 密码登录(手机号/密码 + 滑块)
|
||
- 首次登录强制修改密码
|
||
- 找回密码(纯短信三步流程)
|
||
- 手机验证码登录(MVP 正式功能)
|
||
- 登录失败锁定 / 自动解锁 / 管理员解锁
|
||
- 安全登出与会话失效
|
||
|
||
### 2.2 非目标 / 预留
|
||
|
||
- 微信扫码登录(仅保留禁用入口与接口占位,不开放功能)
|
||
- 企业 SSO(OAuth2 / SAML)
|
||
- 风险评分/设备指纹
|
||
|
||
### 2.3 已废弃(不得实现)
|
||
|
||
- 找回用户名流程(Story 4 已废弃)
|
||
|
||
---
|
||
|
||
## 三、模块架构边界
|
||
|
||
### 3.1 模块职责(`apps/account`)
|
||
|
||
- 租户识别与租户上下文建立前置校验
|
||
- 登录鉴权、会话签发、登出销毁
|
||
- 首登改密门禁(`is_initial_password`)
|
||
- 短信 OTP 发送/校验(找回密码 + 验证码登录)
|
||
- 登录与安全审计事件写入
|
||
|
||
### 3.2 多租户分层职责
|
||
|
||
| 层级 | Schema | 职责 |
|
||
|---|---|---|
|
||
| Tenant Code 校验 | Public | 校验 `tenant_code`、租户状态、品牌信息返回 |
|
||
| 登录认证 | Tenant | 账号鉴权、失败计数、锁定状态、会话签发 |
|
||
| 短信 OTP | Tenant | OTP 记录、场景区分、过期与尝试次数控制 |
|
||
|
||
### 3.3 外部依赖
|
||
|
||
| 依赖模块 | 用途 |
|
||
|---|---|
|
||
| `apps/org` | 员工状态联动(离职/停用不可登录) |
|
||
| `core/encryption.py` | 手机号加密/哈希能力 |
|
||
| `core/cache.py` | 滑块票据、频控、锁定计数、重置 token |
|
||
| `Celery` | 短信发送异步化(可选,建议) |
|
||
|
||
---
|
||
|
||
## 四、API 设计原则
|
||
|
||
1. 登录安全链路:**Tenant 校验 → 滑块验证 → 登录提交**。
|
||
2. 防枚举:账号不存在/停用等敏感状态采用统一外显文案。
|
||
3. 账号锁定是账号维度策略,不区分密码登录或验证码登录。
|
||
4. Redis 仅作运行态与频控,最终状态以数据库持久化字段为准。
|
||
5. 所有登录相关接口遵循 `TECH_STACK/API_CONTRACT.md` 的统一错误响应格式。
|
||
|
||
---
|
||
|
||
## 五、端点清单(对齐 PRD)
|
||
|
||
### 5.1 页面路由(SSR)
|
||
|
||
| 路径 | 方法 | 鉴权 | 说明 |
|
||
|---|---|---|---|
|
||
| `/auth/tenant/identify/` | GET | 否 | Tenant 识别页 |
|
||
| `/auth/login/` | GET | 否 | 登录页(密码登录/验证码登录 Tab) |
|
||
| `/auth/password/forgot/` | GET | 否 | 找回密码三步页 |
|
||
| `/auth/password/change-initial/` | GET | 是 | 首次登录强制改密页 |
|
||
|
||
> 说明:`/auth/wechat/*` 仅预留,不在 MVP 开放。
|
||
|
||
### 5.2 HTMX 片段端点
|
||
|
||
| 路径 | 方法 | 用途 | 返回 |
|
||
|---|---|---|---|
|
||
| `/auth/fragments/captcha/` | GET | 刷新滑块区块 | HTML 片段 |
|
||
| `/auth/fragments/login-form/` | GET | 登录 Tab 内容局刷 | HTML 片段 |
|
||
| `/auth/fragments/forgot-step/` | GET | 找回密码步骤局刷 | HTML 片段 |
|
||
|
||
### 5.3 JSON API(MVP)
|
||
|
||
| 端点 | 方法 | 说明 |
|
||
|---|---|---|
|
||
| `/api/auth/tenant/verify/` | POST | Tenant Code 校验(公开接口) |
|
||
| `/api/auth/captcha/` | GET | 获取滑块拼图验证码(返回背景图 Base64 + 碎片图 Base64 + 验证 Token) |
|
||
| `/api/auth/captcha/verify/` | POST | 校验滑块并签发 `captcha_pass_token` |
|
||
| `/api/auth/login/` | POST | 密码登录 |
|
||
| `/api/auth/login/phone/` | POST | 手机验证码登录 |
|
||
| `/api/auth/recover/password/request/` | POST | 找回密码步骤一:发 OTP |
|
||
| `/api/auth/recover/password/verify/` | POST | 找回密码步骤二:校验 OTP,颁发 `sms_reset_token` |
|
||
| `/api/auth/recover/password/reset/` | POST | 找回密码步骤三:提交新密码 |
|
||
| `/api/auth/password/change-initial/` | POST | 首次登录强制改密提交 |
|
||
| `/api/auth/logout/` | POST | 登出销毁会话 |
|
||
|
||
---
|
||
|
||
## 六、关键流程约束
|
||
|
||
### 6.1 Tenant 识别
|
||
|
||
- `tenant_code` 固定 12 位数字,前后空格自动 trim
|
||
- 成功返回:租户名称、Logo URL、登录地址
|
||
- 失败返回:`valid=false` + 统一错误信息
|
||
- 接口公开但必须限流:单 IP 每分钟 ≤ 10 次
|
||
|
||
### 6.2 密码登录
|
||
|
||
请求体(示例):
|
||
|
||
```json
|
||
{
|
||
"phone": "13800138000",
|
||
"password": "***",
|
||
"captcha_pass_token": "token"
|
||
}
|
||
```
|
||
|
||
关键规则:
|
||
- 滑块通过后方可提交登录
|
||
- 密码连续错误 ≥ 5 次,锁定 30 分钟
|
||
- `is_initial_password = true` 时强制跳转改密页
|
||
- 错误文案统一,不泄露账号存在性细节
|
||
|
||
### 6.3 找回密码(纯短信三步)
|
||
|
||
#### 步骤一:发送 OTP
|
||
- `scene = password_reset`
|
||
- OTP 有效期:10 分钟
|
||
- 同手机号频控:5 次/小时
|
||
- 手机号不存在/停用:统一提示“如该手机号已注册,验证码将在 1 分钟内发送”
|
||
|
||
#### 步骤二:校验 OTP
|
||
- 正确且未过期:签发 `sms_reset_token`(15 分钟,一次性)
|
||
- 错误:累计尝试,≥5 次作废该 OTP
|
||
- 过期:提示重新获取
|
||
|
||
#### 步骤三:重置密码
|
||
- 必须携带有效 `sms_reset_token`
|
||
- 成功后:`is_initial_password = false`
|
||
- 该用户所有会话立即失效,跳回登录页
|
||
|
||
### 6.4 手机验证码登录(MVP 正式)
|
||
|
||
请求体(示例):
|
||
|
||
```json
|
||
{
|
||
"phone": "13800138000",
|
||
"sms_code": "123456"
|
||
}
|
||
```
|
||
|
||
关键规则:
|
||
- 获取登录验证码前必须先通过滑块
|
||
- `scene = login`
|
||
- OTP 有效期:5 分钟
|
||
- 同手机号频控:10 次/小时(与 `password_reset` 独立计数)
|
||
- OTP 错误 ≥5 次作废
|
||
- 账号 `locked` 或 `disabled` 时,验证码登录同样拒绝
|
||
|
||
---
|
||
|
||
## 七、Redis Key 规范(对齐 PRD)
|
||
|
||
| Key | TTL | 说明 |
|
||
|---|---|---|
|
||
| `captcha_token:{uuid}` | 3 分钟 | 滑块验证会话 |
|
||
| `captcha_pass:{uuid}` | 3 分钟 | 一次性通过凭证 |
|
||
| `login_fail:{tenant_id}:{username}` | 30 分钟 | 登录失败计数 |
|
||
| `sms_limit:password_reset:{phone}` | 1 小时 | 找回密码 OTP 发送频控(≤5次) |
|
||
| `sms_limit:login:{phone}` | 1 小时 | 登录 OTP 发送频控(≤10次) |
|
||
| `sms_reset_token:{token}` | 15 分钟 | 找回密码步骤三凭证 |
|
||
| `tenant_verify_ip:{ip}` | 1 分钟 | Tenant 校验接口 IP 限流(≤10次) |
|
||
|
||
---
|
||
|
||
## 八、安全与合规
|
||
|
||
1. 密码仅允许 Django 安全哈希(PBKDF2/Argon2)。
|
||
2. OTP 明文不得入库,仅存哈希。
|
||
3. 敏感字段日志脱敏(手机号、token、验证码)。
|
||
4. 会话过期或登出后,受保护页面必须重定向登录。
|
||
5. HTTPS 强制,不允许明文传输降级。
|
||
|
||
---
|
||
|
||
## 九、错误码建议(登录模块)
|
||
|
||
| code | HTTP | 中文含义 |
|
||
|---|---|---|
|
||
| `AUTH_TENANT_NOT_FOUND` | 400 | 租户识别码无效 |
|
||
| `AUTH_TENANT_RATE_LIMITED` | 429 | Tenant 校验请求过于频繁 |
|
||
| `AUTH_CAPTCHA_INVALID` | 400 | 滑块验证失败 |
|
||
| `AUTH_INVALID_CREDENTIAL` | 401 | 手机号或密码错误 |
|
||
| `AUTH_ACCOUNT_LOCKED` | 423 | 账号已锁定 |
|
||
| `AUTH_ACCOUNT_DISABLED` | 403 | 账号已停用 |
|
||
| `AUTH_SMS_OTP_INVALID` | 400 | 短信验证码错误 |
|
||
| `AUTH_SMS_OTP_EXPIRED` | 400 | 短信验证码过期 |
|
||
| `AUTH_SMS_OTP_RATE_LIMITED` | 429 | OTP 发送超限 |
|
||
| `AUTH_SMS_RESET_TOKEN_INVALID` | 400 | 重置凭证无效或过期 |
|
||
| `AUTH_PASSWORD_WEAK` | 400 | 新密码不满足强度规则 |
|
||
|
||
---
|
||
|
||
## 十、测试映射(与全局编号对齐)
|
||
|
||
- 测试编号规范:`TEST_CASES/TEST_CASE_ID_SPEC.md`
|
||
- 注册表:`TEST_CASES/TEST_CASE_REGISTRY.md`
|
||
- 登录模块用例:`TEST_CASES/TEST_CASES_LOGIN_MODULE.md`
|
||
|
||
建议最小执行集:
|
||
- Tenant 识别:`TC-FON-000001` ~ `000010`
|
||
- 密码登录与锁定:`TC-FON-000011` ~ `000028`
|
||
- 首登改密:`TC-FON-000029` ~ `000033`
|
||
- 找回密码:`TC-FON-000034` ~ `000044`
|
||
- 验证码登录:`TC-FON-000045` ~ `000048`
|
||
|
||
---
|
||
|
||
## 十一、落地顺序建议
|
||
|
||
1. Tenant 识别 + 密码登录主链路
|
||
2. 滑块与失败锁定
|
||
3. 首登改密门禁
|
||
4. 找回密码三步(短信)
|
||
5. 手机验证码登录(scene=login)
|
||
6. 报表与监控补齐
|
||
|
||
---
|
||
|
||
## 十二、文档同步规则
|
||
|
||
- PRD 登录模块变更:同步本文件
|
||
- 数据结构调整:同步 `DATA_MODEL_LOGIN.md`
|
||
- 测试用例新增/变更:同步 `TEST_CASES/TEST_CASE_REGISTRY.md` 与登录用例文档
|
||
- API 契约调整:同步 `TECH_STACK/API_CONTRACT.md`
|