
- 修改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项目配置,调整版本号和启动画面设置。
262 lines
8.7 KiB
Markdown
262 lines
8.7 KiB
Markdown
# 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` - 账户数据模型 |