Files
e-party-iOS/yana/APIs/oauth flow.md
edwinQQQ c470dba79c feat: 更新项目配置和功能模块
- 修改Package.swift以支持iOS 15和macOS 12。
- 更新swift-tca-architecture-guidelines.mdc中的alwaysApply设置为false。
- 注释掉AppDelegate中的NIMSDK导入,移除不再使用的NIMConfigurationManager和NIMSessionManager文件。
- 添加新的API相关文件,包括EMailLoginFeature、IDLoginFeature和相关视图,增强登录功能。
- 更新APIConstants和APIEndpoints以反映新的API路径。
- 添加本地化支持文件,包含英文和中文简体的本地化字符串。
- 新增字体管理和安全工具类,支持AES和DES加密。
- 更新Xcode项目配置,调整版本号和启动画面设置。
2025-07-09 16:14:19 +08:00

262 lines
8.7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# OAuth/Ticket 认证系统 API 文档
## 概述
本文档描述了 YuMi 应用中 OAuth 认证和 Ticket 会话管理的完整流程。系统采用两阶段认证机制:
1. **OAuth 阶段**:用户登录获取 `access_token`
2. **Ticket 阶段**:使用 `access_token` 获取业务会话 `ticket`
## 认证流程架构
### 核心组件
- **AccountInfoStorage**: 负责账户信息和 ticket 的本地存储
- **HttpRequestHelper**: 网络请求管理,自动添加认证头
- **Api+Login**: 登录相关 API 接口
- **Api+Main**: Ticket 获取相关 API 接口
### 认证数据模型
#### AccountModel
```objc
@interface AccountModel : PIBaseModel
@property (nonatomic, assign) NSString *uid; // 用户唯一标识
@property (nonatomic, copy) NSString *jti; // JWT ID
@property (nonatomic, copy) NSString *token_type; // Token 类型
@property (nonatomic, copy) NSString *refresh_token; // 刷新令牌
@property (nonatomic, copy) NSString *netEaseToken; // 网易云信令牌
@property (nonatomic, copy) NSString *access_token; // OAuth 访问令牌
@property (nonatomic, assign) NSNumber *expires_in; // 过期时间
@end
```
## API 接口详情
### 1. OAuth 登录接口
#### 1.1 手机验证码登录
```objc
+ (void)loginWithCode:(HttpRequestHelperCompletion)completion
phone:(NSString *)phone
code:(NSString *)code
client_secret:(NSString *)client_secret
version:(NSString *)version
client_id:(NSString *)client_id
grant_type:(NSString *)grant_type
phoneAreaCode:(NSString *)phoneAreaCode;
```
**接口路径**: `POST /oauth/token`
**请求参数**:
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| phone | String | 是 | 手机号DES加密 |
| code | String | 是 | 验证码 |
| client_secret | String | 是 | 客户端密钥,固定值:"uyzjdhds" |
| version | String | 是 | 版本号,固定值:"1" |
| client_id | String | 是 | 客户端ID固定值"erban-client" |
| grant_type | String | 是 | 授权类型,验证码登录为:"sms_code" |
| phoneAreaCode | String | 是 | 手机区号 |
**返回数据**: AccountModel 对象
#### 1.2 手机密码登录
```objc
+ (void)loginWithPassword:(HttpRequestHelperCompletion)completion
phone:(NSString *)phone
password:(NSString *)password
client_secret:(NSString *)client_secret
version:(NSString *)version
client_id:(NSString *)client_id
grant_type:(NSString *)grant_type;
```
**接口路径**: `POST /oauth/token`
**请求参数**:
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| phone | String | 是 | 手机号DES加密 |
| password | String | 是 | 密码DES加密 |
| client_secret | String | 是 | 客户端密钥 |
| version | String | 是 | 版本号 |
| client_id | String | 是 | 客户端ID |
| grant_type | String | 是 | 授权类型,密码登录为:"password" |
#### 1.3 第三方登录
```objc
+ (void)loginWithThirdPart:(HttpRequestHelperCompletion)completion
openid:(NSString *)openid
unionid:(NSString *)unionid
access_token:(NSString *)access_token
type:(NSString *)type;
```
**接口路径**: `POST /acc/third/login`
**请求参数**:
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| openid | String | 是 | 第三方平台用户唯一标识 |
| unionid | String | 是 | 第三方平台联合ID |
| access_token | String | 是 | 第三方平台访问令牌 |
| type | String | 是 | 第三方平台类型1:Apple, 2:Facebook, 3:Google等 |
### 2. Ticket 获取接口
#### 2.1 获取 Ticket
```objc
+ (void)requestTicket:(HttpRequestHelperCompletion)completion
access_token:(NSString *)accessToken
issue_type:(NSString *)issueType;
```
**接口路径**: `POST /oauth/ticket`
**请求参数**:
| 参数名 | 类型 | 必填 | 描述 |
|--------|------|------|------|
| access_token | String | 是 | OAuth 登录获取的访问令牌 |
| issue_type | String | 是 | 签发类型,固定值:"multi" |
**返回数据**:
```json
{
"code": 200,
"data": {
"tickets": [
{
"ticket": "eyJhbGciOiJIUzI1NiJ9..."
}
]
}
}
```
### 3. HTTP 请求头配置
所有业务 API 请求都会自动添加以下请求头:
```objc
// 在 HttpRequestHelper 中自动配置
- (void)setupHeader {
AFHTTPSessionManager *client = [HttpRequestHelper requestManager];
// 用户ID头
if ([[AccountInfoStorage instance] getUid].length > 0) {
[client.requestSerializer setValue:[[AccountInfoStorage instance] getUid]
forHTTPHeaderField:@"pub_uid"];
}
// Ticket 认证头
if ([[AccountInfoStorage instance] getTicket].length > 0) {
[client.requestSerializer setValue:[[AccountInfoStorage instance] getTicket]
forHTTPHeaderField:@"pub_ticket"];
}
// 其他公共头
[client.requestSerializer setValue:[NSBundle uploadLanguageText]
forHTTPHeaderField:@"Accept-Language"];
[client.requestSerializer setValue:PI_App_Version
forHTTPHeaderField:@"App-Version"];
}
```
## 使用流程
### 完整登录流程示例
```objc
// 1. 用户登录获取 access_token
[Api loginWithCode:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
// 保存账户信息
AccountModel *accountModel = [AccountModel modelWithDictionary:data.data];
[[AccountInfoStorage instance] saveAccountInfo:accountModel];
// 2. 使用 access_token 获取 ticket
[Api requestTicket:^(BaseModel * _Nullable data, NSInteger code, NSString * _Nullable msg) {
if (code == 200) {
NSArray *tickets = [data.data valueForKey:@"tickets"];
NSString *ticket = [tickets[0] valueForKey:@"ticket"];
// 保存 ticket
[[AccountInfoStorage instance] saveTicket:ticket];
// 3. 登录成功,可以进行业务操作
[self navigateToMainPage];
}
} access_token:accountModel.access_token issue_type:@"multi"];
}
} phone:encryptedPhone
code:verificationCode
client_secret:@"uyzjdhds"
version:@"1"
client_id:@"erban-client"
grant_type:@"sms_code"
phoneAreaCode:areaCode];
```
### 自动登录流程
```objc
- (void)autoLogin {
// 检查本地是否有账户信息
AccountModel *accountModel = [[AccountInfoStorage instance] getCurrentAccountInfo];
if (accountModel == nil || accountModel.access_token == nil) {
[self tokenInvalid]; // 跳转到登录页
return;
}
// 检查是否有有效的 ticket
if ([[AccountInfoStorage instance] getTicket].length > 0) {
[[self getView] autoLoginSuccess];
return;
}
// 使用 access_token 重新获取 ticket
[Api requestTicket:^(BaseModel * _Nonnull data) {
NSArray *tickets = [data.data valueForKey:@"tickets"];
NSString *ticket = [tickets[0] valueForKey:@"ticket"];
[[AccountInfoStorage instance] saveTicket:ticket];
[[self getView] autoLoginSuccess];
} fail:^(NSInteger code, NSString * _Nullable msg) {
[self logout]; // ticket 获取失败,重新登录
} access_token:accountModel.access_token issue_type:@"multi"];
}
```
## 错误处理
### 401 未授权错误
当接收到 401 状态码时,系统会自动处理:
```objc
// 在 HttpRequestHelper 中
if (response && response.statusCode == 401) {
failure(response.statusCode, YMLocalizedString(@"HttpRequestHelper7"));
// 通常需要重新登录
}
```
### Ticket 过期处理
- Ticket 过期时服务器返回 401 错误
- 客户端应该使用保存的 `access_token` 重新获取 ticket
- 如果 `access_token` 也过期,则需要用户重新登录
## 安全注意事项
1. **数据加密**: 敏感信息(手机号、密码)使用 DES 加密传输
2. **本地存储**:
- `access_token` 存储在文件系统中
- `ticket` 存储在内存中,应用重启需重新获取
3. **请求头**: 所有业务请求自动携带 `pub_uid``pub_ticket`
4. **错误处理**: 建立完善的 401 错误重试机制
## 相关文件
- `YuMi/Structure/MVP/Model/AccountInfoStorage.h/m` - 账户信息存储管理
- `YuMi/Modules/YMLogin/Api/Api+Login.h/m` - 登录相关接口
- `YuMi/Modules/YMTabbar/Api/Api+Main.h/m` - Ticket 获取接口
- `YuMi/Network/HttpRequestHelper.h/m` - 网络请求管理
- `YuMi/Structure/MVP/Model/AccountModel.h/m` - 账户数据模型