iOS应用集成SDK

您需要在应用中集成SDK,才能在控制台BOT管理中配置App防爬场景化规则。本文介绍了如何为iOS应用集成WAF App防护SDK(以下简称SDK)。

背景信息

App防护SDK主要用于对通过App客户端发起的请求进行签名。WAF服务端通过校验App请求签名,识别App业务中的风险、拦截恶意请求,实现App防护的目的。

关于App防护提供的SDK所涉及的隐私政策,请参见Web应用防火墙App防护SDK隐私政策

使用限制

  • iOS SDK分为IDFA(Identifier for Advertising,简称IDFA) 版本和非IDFA版本,对应的SDK文件分别是:

    • AliTigerTally_IDFA.framework

    • AliTigerTally_NOIDFA.framework

    如果您的iOS项目中使用了IDFA,推荐您集成AliTigerTally_IDFA版本SDK,否则请使用AliTigerTally_NOIDFA版本SDK。

  • init初始化接口存在耗时操作,为确保安全能力完整性,建议在调用init接口后,确保至少间隔2秒再调用后续的vmpSign签名接口。此间隔为推荐值(非强制要求),旨在提升SDK的防护效果。实际调用中可根据业务需求灵活调整,但缩短间隔可能影响安全能力的完整生效。

  • iOS应用对应的iOS版本是9.0及以上,否则不支持集成App防护SDK。

前提条件

  • 已获取iOS应用对应的SDK。

    获取方法:请提交工单,联系产品技术专家获取SDK。

    说明

    iOS应用对应的SDK文件名为tigertally-X.Y.Z-xxxx-ios.zip,X.Y.Z表示版本号,其中包含2framework文件,2xcframework文件。

  • 已获取SDK认证密钥(即appkey)。

    开启BOT管理后,即可在BOT管理 > App防护列表中,单击获取并复制appkey,获取SDK认证密钥。该密钥用于发起SDK初始化请求,需要在集成代码中使用。

    image

    说明

    每个阿里云账号拥有唯一的appkey(适用于所有接入WAF防护的域名),且Android、iOSHarmony应用集成SDK时都使用该appkey。

    认证密钥示例:****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****。

步骤一:新建工程

Xcode环境为例,新建一个iOS工程,并按照配置向导完成创建。创建好的工程目录如下图所示。image.png

步骤二:集成framework

将获取到的SDK文件AliTigerTally_IDFA.frameworkAliTigerTally_NOIDFA.framework添加到相应项目。

  • IDFA版本image.png

  • IDFA版本image.png

将获取到的验证码模块SDK文件AliCaptcha.framework添加到相应项目。

image.png

将资源文件AliCaptcha.bundle添加到相应项目。

image.png

步骤三:添加依赖库

依赖库

IDFA版本是否需要

IDFA版本是否需要

libc++.tbd

libresolv.9.tbd

CoreTelephony.framework

AdSupport.framework

AppTrackingTransparency.framework

image.png

步骤四:编辑选项

Other Linker Flags选项中添加-ObjCimage.png

步骤五:添加集成代码

1. 添加头文件

  • IDFA版本配置信息如下:

    #import <AliTigerTally_IDFA/AliTigerTally.h>
  • IDFA版本配置信息如下:

    #import <AliTigerTally_NOIDFA/AliTigerTally.h> 

2. 设置数据签名

  1. 设置您业务中自定义的终端用户标识,方便您更灵活地配置WAF防护策略。

    /**
    * 设置用户账户
    *
    * @param account   账户信息
    */
    - (void)setAccount:(NSString *)account;
    • 参数说明:

      • account:NSString类型,表示标识一个用户的字符串,建议您使用脱敏后的格式。

    • 返回值:无返回,或返回void。

    • 示例代码:

      // 游客身份可以暂时先不setAccount, 直接初始化; 登录以后调用setAccount和重新初始化
      [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
  2. 初始化SDK,执行一次初始化采集。

    一次初始化采集表示采集一次终端设备信息,您可以根据业务的不同,重新调用init函数进行初始化采集。

    初始化采集分为三种模式:全量采集、自定义隐私采集、非隐私采集(不采集涉及终端设备用户隐私的字段,包括:IDFA、IDFV)。

    说明

    建议在符合内部合规要求的前提下,选择适配的采集模式,确保数据采集的完整性。完整数据有助于更有效地识别潜在风险。

    // 初始化回调, 回调返回接口调用状态码
    typedef void (^TTInitListener)(int);
    
    /**
     * SDK 初始化
     *
     * @param appkey        密钥
     * @param options       可选参数
     * @param onInitFinish  初始化完毕回调
     * @return 是否初始化成功
     */
    - (int)init:(NSString *)appkey collectType:(TTCollectType)type options:(NSMutableDictionary *_Nullable)options listener:(TTInitListener _Nullable)onInitFinish;
    • 参数说明:

      • appkey:NSString类型,设置为您的SDK认证密钥。

      • collectType:TTCollectType类型,设置采集模式。取值:

        字段名

        说明

        示例

        TT_DEFAULT

        表示采集全量数据。

        TT_DEFAULT

        TT_NO_BASIC_DATA

        表示不采集基础设备数据。

        包括:设备名称、系统版本号、屏幕分辨率。

        TT_NO_X | TT_NO_Y

        (表示既不采集X又不采集Y, X、Y表示具体采集项的字段类型名)

        TT_NO_UNIQUE_DATA

        表示不采集唯一标识数据。

        包括:IDFV、IDFA。

        TT_NO_EXTRA_DATA

        表示不采集扩展设备数据。

        包括:连接的WIFI信息(SSID、BSSID)、附近WIFI列表。

        TT_NOT_GRANTED

        表示不采集以上所有隐私数据。

        TT_NOT_GRANTED

      • options:NSMutableDictionary类型,信息采集可选项,默认可以为nil。可选参数如下

        字段名

        说明

        示例

        IPv6

        是否使用IPv6域名上报设备信息。

        • 0(默认):使用IPv4域名。

        • 1:使用IPv6域名。

        1

        Intl

        是否使用非中国内地域名上报设备信息。

        • 0(默认):中国内地上报。

        • 1:非中国内地上报。

        1

        CustomUrl

        设置数据上报服务器域名

        https://cloudauth-device.us-west-1.aliyuncs.com

        CustomHost

        设置数据上报服务器host

        cloudauth-device.us-west-1.aliyuncs.com

        说明

        常见国际站点设置Intl参数即可,只有指定站点上报需要设置CustomUrlCustomHost,例如美西站点:https://cloudauth-device.us-west-1.aliyuncs.com。

      • listener:TTInitListener类型,SDK初始化回调接口,可在回调中判断初始化结果的具体状态,默认可以传nil。

        TTCode

        Code

        备注

        TT_SUCCESS

        0

        SDK初始化成功

        TT_NOT_INIT

        -1

        SDK未调用初始化

        TT_NOT_PERMISSION

        -2

        SDK需要的iOS基础权限未完全授权

        TT_UNKNOWN_ERROR

        -3

        系统未知错误

        TT_NETWORK_ERROR

        -4

        网络错误

        TT_NETWORK_ERROR_EMPTY

        -5

        网络错误,返回内容为空串

        TT_NETWORK_ERROR_INVALID

        -6

        网络返回的格式非法

        TT_PARSE_SRV_CFG_ERROR

        -7

        服务端配置解析失败

        TT_NETWORK_RET_CODE_ERROR

        -8

        网关返回失败

        TT_APPKEY_EMPTY

        -9

        AppKey为空

        TT_PARAMS_ERROR

        -10

        其他参数错误

        TT_FGKEY_ERROR

        -11

        密钥计算错误

        TT_APPKEY_ERROR

        -12

        SDK版本和AppKey版本不匹配

    • 返回值:int类型,返回错误码。0表示成功;负数表示失败。

    • 示例代码:

      // appkey代表阿里云客户平台分配的认证密钥
      NSString *appKey = @"xxxxxxxxxxxxxxxxxxxxx";
      // 可选参数, 可配置IPv6和国际上报
      NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
      [options setValue:@"0" forKey:@"IPv6"];     // 配置为IPv4
      [options setValue:@"0" forKey:@"Intl"];     // 配置为中国内地上报
      // [options setValue:@"1" forKey:@"Intl"];  // 配置为非中国内地上报
      // 美西站点上报
      // [options setValue:@"https://cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomUrl"];
      // [options setValue:@"cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomHost"];
      
      // 一次初始化调用, 代表一次设备信息采集, 可以根据业务的不同, 重新调用函数init初始化采集
      // 全量采集
      if (0 == [[AliTigerTally sharedInstance] init:appkey collectType:TT_DEFAULT options:options listener:nil]) {
          NSLog(@"初始化成功");
      } else {
          NSLog(@"初始化失败");
      }
      
      // 指定隐私数据采集, 不同的隐私数据可以通过"|"进行拼接
      TTCollectType collectPrivacy = TT_NO_BASIC_DATA | TT_NO_EXTRA_DATA;
      int ret = [[AliTigerTally sharedInstance] init:appkey collectType:collectPrivacy options:options listener:nil];
      
      // 不采集隐私字段
      int ret = [[AliTigerTally sharedInstance] init:appkey collectType:TT_NOT_GRANTED options:options listener:nil];
  3. 数据哈希。

    自定义加签接口,对传入的数据input进行计算,生成一个whash字符串作为自定义签名数据。Post、Put、Patch请求需要传入request body,Get、Delete请求传入完整的URL地址。同时,whash字符串需要添加到HTTP请求headerali_sign_whash中。

    // 请求类型:
    typedef NS_ENUM(NSInteger, TTRequestType) {
        TT_GET=0, TT_POST, TT_PUT, TT_PATCH, TT_DELETE
    };
    
    /**
     * 自定义签名数据 hash
     * @param type  数据类型
     * @param input 签名数据
     * @return whash
     */
    - (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;
    • 参数说明:

      • type:TTTypeRequest类型,设置数据类型。取值:

        • GET:表示Get请求数据。

        • POST:表示Post请求数据。

        • PUT:表示Put请求数据。

        • PATCH:表示Patch请求数据。

        • DELETE:表示Delete请求数据。

      • input:NSData类型,表示待加签的数据,根据type传入body或者URL。

    • 返回值:NSString类型,返回whash字符串。

    • 示例代码

      // get 请求
      NSString *url = @"https://tigertally.aliyun.com/apptest";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@", whash);
      
      // post 请求
      NSString *body = @"hello world";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@", whash);
    说明

    控制台配置默认签名(即不勾选自定义加签)不需要调用该接口,勾选自定义加签时需要在数据签名前调用该接口进行哈希校验。

  4. 数据签名。

    使用vmp技术对input的数据进行签名处理,并且返回wtoken字符串用于请求认证。

    /**
     * 数据签名
     * @param input 签名数据
     * @return wtoken
     */
    - (NSString *)vmpSign:(NSData *)input;
    • 参数说明:

      • input:NSData类型,表示待签名的数据,一般是整个请求体的request body,或者是自定义加签的whash

    • 返回值:NSString类型,返回wtoken字符串。

    • 示例代码:

      // 控制台配置默认签名 (不勾选自定义加签)
      NSString *body = @"hello world";
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"wtoken: %@", wtoken);
      
      // 控制台配置自定义加签
      // 自定义签名 post请求
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@, wtoken: %@", whash, wtoken);
      
      // 自定义签名 get请求
      NSString *url = @"https://tigertally.aliyun.com/apptest";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]];
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@, wtoken: %@", whash, wtoken);
      说明
      • 调用vmpHash进行自定义加签时,签名接口vmpSign的参数input为生成的whash字符串,且在配置App防爬场景化策略时,自定义加签字段的值需设置为ali_sign_whash。

      • 调用vmpHash生成Get请求的whash时,必须保证输入的URL地址和最终网络请求的URL一致,特别需要注意UrlEncode情况,部分框架会自动对中文或者参数进行UrlEncode编码。

      • 接口vmpHash的参数input不支持字节或者空字符串,输入为URL时必须存在Path或者Param。

      • 调用vmpSign时,如果请求体为空(例如,Post请求或Get请求的body为空),则填写空对象nil或空字符串的NSData值(例如[@"" dataUsingEncoding:NSUTF8StringEncoding])。

      • whashwtoken为以下字符串时表示初始化流程存在异常:

        • you must call init first:表示未调用init函数。

        • you must input correct data:表示传入数据错误。

        • you must input correct type:表示传入类型错误。

3. 二次校验

  1. 判断结果。

    根据responsecookiebody字段判断是否要进行二次校验。header中可能存在多个Set-Cookie,需要按照cookie格式合并后调用该接口。

    /**
     * 是否进行二次校验
     *
     * @param cookie  response cokie
     * @param body    response body
     * @return 0:通过 1:二次校验
     */
    - (int)cptCheck:(NSString *)cookie body:(NSData *)body;
    • 参数说明:

      • cookie:NSString类型,设置请求response中全部cookie

      • body:NSData类型,设置请求response中全部body

    • 返回值:int类型,返回决策结果,0表示通过,1表示二次校验。

      • 示例代码:

      NSString *cookie = @"key1=value1;key2=value2;";
      NSData *body = xxx;
      int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
      NSLog(@"recheck: %d", recheck);
  2. 创建滑块。

    根据cptCheck返回结果决定是否要创建一个滑块对象,TTCaptcha对象提供showdismiss方法,对应显示滑块和隐藏滑块窗口。TTOption封装了滑块可配置的参数,TTListener包含了滑块的2种回调状态。如果需要自定义滑块窗口页面需要传入自定义页面地址,支持本地HTML文件,或者远程页面。

    /**
     * 显示滑块验证
     *
     * @param view      父组件
     * @param option    参数
     * @param detegate  回调协议
     */
    - (TTCaptcha *)cptCreate:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)detegate;
    
    
    @protocol TTDelegate <NSObject>
    @required
    // 滑块验证成功
    - (void)success:(TTCaptcha *)captcha data:(NSString *)data;
    
    // 滑块验证失败
    - (void)failed:(TTCaptcha *)captcha code:(NSString *)code;
    @end
    
    
    @interface TTOption : NSObject
    // 点击取消
    @property (nonatomic, assign) BOOL cancelable;
    
    // 自定义页面
    @property (nonatomic, strong) NSString *customUri;
    
    // 语言
    @property (nonatomic, strong) NSString *language;
    @end
    
    
    @interface TTCaptcha : NSObject
    
    - (instancetype)init:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)delegate;
    
    // 获取滑块traceId, 用于数据统计
    - (NSString *)getTraceId;
    
    // 滑块调用显示
    - (void)show;
    
    // 滑块取消
    - (void)dismiss;
    
    @end
    • 参数说明:

      • view:View类型,设置当前页面view。

      • option:TTOption类型,设置滑块配置参数。

      • listener:TTDelegate类型,设置滑块状态回调。

    • 返回值:TTCaptcha类型,返回滑块对象。

    • 示例代码:

      #pragma mark - TTDelegate
      - (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
          NSLog(@"captcha failed: %@", code);
      }
      
      - (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
          NSLog(@"captcha success: %@", data);
      }
      
      TTOption *option = [[TTOption alloc] init];
      // option.customUri = @"ali-tt-captcha-demo-ios";
      option.language   = @"cn";
      option.cancelable = true;
      
      TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self];
      [captcha show];
      说明

      验证失败,表示用户滑动过程中或结束后检测到异常情况。

      具体错误码如下所示:

      • 1001:验证失败判定不通过。

      • 1002:系统异常。

      • 1003:参数错误

      • 1005:验证取消

      • 8001:滑块唤起错误。

      • 8002:滑块验证数据异常。

      • 8003:滑块验证内部异常。

      • 8004:网络错误。

最佳实践示例

#import "DemoController.h"
#if __has_include(<AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>)
    #import <AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>
#else
    #import <AliTigerTally_IDFA/AliTigerTally_IDFA.h>
#endif

@interface DemoController () <TTDelegate>

@end

static NSString *kAppHost = @"******";
static NSString *kAppUrl  = @"******";
static NSString *kAppkey  = @"******";


@implementation DemoController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.


    [self doTest];
}

- (void)doTest {

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        // 初始化
        NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
        // [options setValue:@"1" forKey:@"Intl"];     // 配置为国际上报
        int code = [[AliTigerTally sharedInstance] init:kAppkey collectType:TT_DEFAULT options:options listener:nil];
        NSLog(@"tigertally init: %d", code);

        // 不能立即同步调用
        [NSThread sleepForTimeInterval:2.0];

        // 签名
        NSString *body = @"hello world";
        NSString *whash = nil, *wtoken = nil;
        // 自定义加签
        whash  = [[AliTigerTally sharedInstance] vmpHash:TT_Post input:[body dataUsingEncoding:NSUTF8StringEncoding]];
        wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
        NSLog(@"tigertally vmp: %@, %@", whash, wtoken);

        // 默认正常加签
        // wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
        // NSLog(@"tigertally vmp: %@", wtoken);

        [self doPost:kAppUrl host:kAppHost whash:whash wtoken:wtoken body:[body dataUsingEncoding:NSUTF8StringEncoding] callback:^(NSInteger code, NSString * _Nonnull cookie, NSData * _Nonnull body) {
            int check = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
            NSLog(@"captcha result:%d", check);

            if (check == 0) return;
            [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                [self doShow];
            }];
        }];
    }];
    [thread start];
}

- (void)doShow {
    NSLog(@"captcha show");
    
    TTOption *option = [[TTOption alloc] init];
//    option.customUri = @"ali-tt-captcha-demo-ios";
    option.language   = @"cn";
    option.cancelable = true;
    
    TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self];
    [captcha show];
}

- (void)doPost:(NSString *)url host:(NSString *)host whash:(NSString *)whash wtoken:(NSString *)wtoken body:(NSData *)body callback:(void(^)(NSInteger code, NSString* cookie, NSData *body))callback {
    NSLog(@"start reqeust post");
    
    NSURL* requestUrl = [NSURL URLWithString: url];
    NSData* requestBody = body;
    
    NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    [request setValue: @"text/x-markdown" forHTTPHeaderField: @"Content-Type"];
    [request setValue: host forHTTPHeaderField: @"HOST"];
    [request setValue: wtoken forHTTPHeaderField:@"wToken"];
    if (whash) {
        [request setValue:whash forHTTPHeaderField:@"ali_sign_whash"];
    }
    request.HTTPMethod = @"post";
    request.HTTPBody = requestBody;
    
    NSURLSessionDataTask* dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"tiger tally sign failed: %@", [error description]);
            callback(-1, nil, [[error description] dataUsingEncoding:NSUTF8StringEncoding]);
            return;
        }
        
        NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
        
        NSInteger code = httpResponse.statusCode;
        NSString* body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSMutableString* cookies = [[NSMutableString alloc] init];
        for (NSHTTPCookie* cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
            if ([url containsString:[cookie domain]]) {
                NSLog(@"domain:%@, path: %@, name:%@, value: %@", [cookie domain], [cookie path], [cookie name], [cookie value]);
                [cookies appendFormat: @"%@=%@;", [cookie name], [cookie value]];
            }
        }
        NSLog(@"reponse code: %ld", code);
        NSLog(@"reponse cookie: %@", cookies);
        NSLog(@"reponse body: %@", body ? (body.length > 100 ? [body substringToIndex:100]:body) : @"");
        
        callback(code, cookies, data);
    }];
    [dataTask resume];
}

#pragma mark - TTDelegate
- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
    NSLog(@"captcha failed: %@", code);

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:code preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:true completion:nil];
}

- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
    NSLog(@"captcha success: %@", data);

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:data preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:true completion:nil];
}

@end