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

8.7 KiB
Raw Blame History

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

@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 手机验证码登录

+ (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 手机密码登录

+ (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 第三方登录

+ (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

+ (void)requestTicket:(HttpRequestHelperCompletion)completion 
         access_token:(NSString *)accessToken 
           issue_type:(NSString *)issueType;

接口路径: POST /oauth/ticket

请求参数:

参数名 类型 必填 描述
access_token String OAuth 登录获取的访问令牌
issue_type String 签发类型,固定值:"multi"

返回数据:

{
  "code": 200,
  "data": {
    "tickets": [
      {
        "ticket": "eyJhbGciOiJIUzI1NiJ9..."
      }
    ]
  }
}

3. HTTP 请求头配置

所有业务 API 请求都会自动添加以下请求头:

// 在 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"];
}

使用流程

完整登录流程示例

// 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];

自动登录流程

- (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 状态码时,系统会自动处理:

// 在 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_uidpub_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 - 账户数据模型