- 文档从扁平结构迁移至分类目录 (api-server/ios-projects/web-projects/长期规划) - 更新总待完成清单 (B1-B6 全部完成, I1-I7 全部完成) - 新增后端实现状态、已实现功能汇总等已完成文档 - 新增 iOS 功能需求清单、架构设计、差距分析等文档 - 清理旧版未维护文档 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
173 lines
4.3 KiB
Markdown
173 lines
4.3 KiB
Markdown
# iOS 登录流程 —— iOS 端集成
|
||
|
||
---
|
||
|
||
## 一、iOS 需要的核心组件
|
||
|
||
```
|
||
AuthService ← 调后端登录接口
|
||
UserService ← 用户信息管理
|
||
TokenStore ← token 存储协议
|
||
KeychainTokenStore ← 基于 Keychain 的安全存储实现
|
||
AppSession ← 管理当前登录态
|
||
```
|
||
|
||
---
|
||
|
||
## 二、数据存储策略
|
||
|
||
| 数据 | 存储位置 | 生命周期 | 原因 |
|
||
|------|---------|---------|------|
|
||
| `accessToken` | 内存 | App 运行期间 | 短期使用,不需要持久化 |
|
||
| `refreshToken` | Keychain | 长期持久化 | 敏感凭证,需安全存储,卸载后也保留 |
|
||
| `user` | AppSession / UserStore | App 运行期间 | 用户展示信息 |
|
||
|
||
---
|
||
|
||
## 三、App 启动流程
|
||
|
||
```
|
||
App 启动
|
||
→ AppSession.checkSession()
|
||
→ 从 Keychain 读取 refreshToken
|
||
→ 如果没有 refreshToken
|
||
→ 进入登录页
|
||
→ 如果有 refreshToken
|
||
→ 调用 POST /api/auth/refresh
|
||
→ 成功
|
||
→ 存储新的 accessToken + refreshToken
|
||
→ 调用 GET /api/users/me
|
||
→ 存储 user 信息
|
||
→ 进入主界面
|
||
→ 失败
|
||
→ 清空 Keychain + 内存 token
|
||
→ 进入登录页
|
||
```
|
||
|
||
---
|
||
|
||
## 四、登录流程
|
||
|
||
### 开发登录(dev-login)
|
||
|
||
```
|
||
用户在登录页输入邮箱/昵称
|
||
→ AuthService.devLogin(email, nickname)
|
||
→ POST /api/auth/dev-login
|
||
→ 后端返回 { accessToken, refreshToken, user }
|
||
→ refreshToken 存 Keychain
|
||
→ accessToken 放内存
|
||
→ user 放 AppSession
|
||
→ 进入主界面
|
||
```
|
||
|
||
### Apple 登录
|
||
|
||
```
|
||
用户点击 Sign in with Apple
|
||
→ iOS 系统弹出 Apple 授权界面
|
||
→ 用户授权成功
|
||
→ 拿到 identityToken + authorizationCode 等
|
||
→ AuthService.appleLogin(identityToken, ...)
|
||
→ POST /api/auth/apple
|
||
→ 后端验证 Apple token,返回 { accessToken, refreshToken, user }
|
||
→ refreshToken 存 Keychain
|
||
→ accessToken 放内存
|
||
→ user 放 AppSession
|
||
→ 进入主界面
|
||
```
|
||
|
||
---
|
||
|
||
## 五、接口请求拦截
|
||
|
||
所有需要登录的接口都必须携带:
|
||
|
||
```http
|
||
Authorization: Bearer {accessToken}
|
||
```
|
||
|
||
### HTTP Client 封装建议
|
||
|
||
```
|
||
所有请求自动注入 Authorization Header
|
||
→ 从 AuthService 获取当前 accessToken
|
||
→ 自动添加到请求头
|
||
```
|
||
|
||
### 401 自动处理
|
||
|
||
```
|
||
接口返回 401
|
||
→ 调用 POST /api/auth/refresh
|
||
→ 成功
|
||
→ 更新 accessToken
|
||
→ 自动重试原请求
|
||
→ 失败
|
||
→ 清空 Keychain + 内存数据
|
||
→ 跳转登录页
|
||
```
|
||
|
||
**重要**:重试原请求时注意避免无限循环,设置最多重试 1 次。
|
||
|
||
---
|
||
|
||
## 六、退出登录
|
||
|
||
```
|
||
用户点击退出登录
|
||
→ AuthService.logout()
|
||
→ POST /api/auth/logout
|
||
Body: { refreshToken: 从 Keychain 取的 refreshToken }
|
||
Header: Authorization: Bearer accessToken
|
||
→ 后端标记 refreshToken revoked
|
||
→ iOS 端:
|
||
→ 清除 Keychain 中的 refreshToken
|
||
→ 清除内存中的 accessToken
|
||
→ 清除 AppSession 中的 user
|
||
→ 跳转登录页
|
||
```
|
||
|
||
---
|
||
|
||
## 七、Token 存储对比:UserDefaults vs Keychain
|
||
|
||
| | UserDefaults | Keychain |
|
||
|------|------------|----------|
|
||
| 安全性 | 低(明文存储) | 高(系统级加密) |
|
||
| 应用卸载后 | 数据被清除 | 可选保留(推荐保留) |
|
||
| 备份 | 包含在 iTunes/iCloud 备份中 | 仅加密备份 |
|
||
| 适用数据 | 非敏感偏好设置 | 密码、Token 等敏感凭据 |
|
||
|
||
**结论:refreshToken 一定要用 Keychain 存储。**
|
||
|
||
---
|
||
|
||
## 八、Session 状态机
|
||
|
||
```
|
||
App 启动
|
||
│
|
||
▼
|
||
┌─────────────────┐
|
||
│ 检查 Keychain │
|
||
│ 有 refreshToken? │
|
||
└───────┬─────────┘
|
||
│
|
||
┌───────┴───────┐
|
||
│ 有 │ 无
|
||
▼ ▼
|
||
┌─────────┐ ┌──────────┐
|
||
│ 调 refresh │ │ 进入登录页 │
|
||
│ 接口 │ └──────────┘
|
||
└─────┬─────┘
|
||
│
|
||
┌────┴────┐
|
||
│ 成功 │ 失败
|
||
▼ ▼
|
||
┌────────┐ ┌──────────┐
|
||
│ 调 /me │ │ 清空数据 │
|
||
│ 进主页 │ │ 进登录页 │
|
||
└────────┘ └──────────┘
|
||
```
|