您需要在应用中集成SDK,才能在控制台BOT管理中配置App防爬场景化规则。本文介绍了如何为iOS应用集成WAF App防护SDK(以下简称SDK)。
背景信息
App防护SDK主要用于对通过App客户端发起的请求进行签名。WAF服务端通过校验App请求签名,识别App业务中的风险、拦截恶意请求,实现App防护的目的。
使用限制
iOS SDK分为IDFA(Identifier for Advertising,简称IDFA) 版本和非IDFA版本,对应的SDK文件分别是:
AliTigerTally_IDFA.framework
AliTigerTally_NOIDFA.framework
如果您的iOS项目中使用了IDFA,推荐您集成AliTigerTally_IDFA版本SDK,否则请使用AliTigerTally_NOIDFA版本SDK。
init初始化接口存在耗时操作,调用后不能立即同步调用vmpSign接口。
iOS应用对应的iOS版本是9.0及以上,否则不支持集成App防护SDK。
前提条件
已获取iOS应用对应的SDK。
获取方法:请提交工单,联系产品技术专家获取SDK。
已获取SDK认证密钥(即
appkey
)。开启BOT管理后,即可在新建或编辑防护模板的防护场景定义配置导向的APP SDK集成中单击获取并复制appkey,获取SDK认证密钥。该密钥用于发起SDK初始化请求,需要在集成代码中使用。
说明每个阿里云账号拥有唯一的
appkey
(适用于所有接入WAF防护的域名),且Android和iOS应用集成SDK时都使用该appkey
。认证密钥示例:
****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****
步骤一:新建工程
以Xcode环境为例,新建一个iOS工程,并按照配置向导完成创建。创建好的工程目录如下图所示。
步骤二:集成framework包
将获取到的SDK文件AliTigerTally_IDFA.framework或AliTigerTally_NOIDFA.framework拖放到相应项目。
IDFA版本
无IDFA版本
步骤三:添加依赖库
依赖库 | IDFA版本是否需要 | 无IDFA版本是否需要 |
libc++.tbd | 是 | 是 |
CoreTelephony.framework | 是 | 是 |
libresolv.9.tbd | 是 | 是 |
AdSupport.framework | 是 | 否 |
步骤四:编辑选项
在Other Linker Flags选项中添加-ObjC。
步骤五:添加集成代码
一、添加头文件
IDFA版本配置信息如下:
#import <AliTigerTally_IDFA/AliTigerTally.h>
非IDFA版本配置信息如下:
#import <AliTigerTally_NOIDFA/AliTigerTally.h>
如果是Swift语言,需要在编译选项Objective-C Bridging Header添加创建的头文件。
二、设置数据签名
设置您业务中自定义的终端用户标识,方便您更灵活地配置WAF防护策略。
参数说明:account,String类型,表示标识一个用户的字符串,建议您使用脱敏后的格式。
返回值:无返回,或返回void。
示例代码:
初始化SDK,执行一次初始化采集。
一次初始化采集表示采集一次终端设备信息,您可以根据业务的不同,重新调用
init
函数进行初始化采集。参数说明:appkey:String类型,设置为您的SDK认证密钥。
返回值:int类型,返回错误码。0表示成功;负数表示失败。
示例代码:
数据哈希。
自定义加签使用接口,将对传入的数据计算生成一个 whash字符串,Post、Put、Patch请求需要传入request body,Get、Delete请求传入完整的URL地址。同时whash字符串需要添加到http请求header的ali_sign_whash中。
参数说明:
type:TTTypeRequest类型,设置数据类型。取值:
GET:表示Get请求数据。
POST:表示Post请求数据。
PUT:表示Put请求数据。
PATCH:表示Patch请求数据。
DELETE:表示Delete请求数据。
data:byte[]类型,表示待加签的数据,根据type传入body或者URL。
返回值:String类型,返回whash字符串。
示例代码:
数据签名。
使用vmp技术对input的数据进行签名处理,并且返回wtoken字符串。
参数说明:input:byte[]类型,表示待签名的数据,或者,自定义签名的whash。
返回值:String类型,返回wtoken字符串。
示例代码:
/**
* 设置用户账户
*
* @param account 账户信息
*/
- (void)setAccount:(NSString *)account;
//游客身份可以暂时先不setAccount,直接初始化;登录以后调用setAccount和重新初始化
[[AliTigerTally sharedInstance] setAccount:@"testAccount"];
/**
* SDK 初始化
*
* @param appkey 密钥
* @return 是否初始化成功
*/
- (int)initialize:(NSString *)appkey;
// appkey代表阿里云客户平台分配的认证密钥
// 一次初始化采集,代表一次设备信息采集,可以根据业务的不同,重新调用函数initialize初始化采集
NSString *appKey = @"xxxxxxxxxxxxxxxxxxxxx";
if (0 == [[AliTigerTally sharedInstance] initialize:appKey]){
NSLog(@"初始化成功");
} else {
NSLog(@"初始化失败");
}
// 请求类型:
typedef NS_ENUM(NSInteger, TTTypeRequest) {
TTTypeGet=0, TTTypePost, TTTypePut, TTTypePatch, TTTypeDelete
};
/**
* 自定义签名数据 hash
* @param type 数据类型
* @param input 签名数据
* @return whash
*/
- (NSString *)vmpHash:(TTTypeRequest)type input:(NSData *)input;
// get 请求
NSString *url = @"https://tigertally.aliyun.com/apptest";
NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TTTypeGet data:[url dataUsingEncoding:NSUTF8StringEncoding]];
NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(@"whash: %@, wtoken: %@", whash, wtoken);
// post 请求
NSString *body = @"hello world";
NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TTTypePost data:[body dataUsingEncoding:NSUTF8StringEncoding]];
NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(@"whash: %@, wtoken: %@", whash, wtoken);
/**
* 数据签名
* @param input 签名数据
* @return wtoken
*/
- (NSString *)vmpSign:(NSData *)input;
NSString *body = @"hello world";
NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(@"wtoken: %@", 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为空),则填写空对象null或空字符串的Bytes值(例如"".getBytes("UTF-8"))。
当whash或wtoken为以下字符串时表示初始化流程存在异常:
you must call init first:表示未调用init函数。
you must input correct data:表示传入数据错误。
you must input correct type:表示传入类型错误。
三、二次校验
判断结果。
根据response中cookie和body字段判断是否要进行二次校验。header中可能存在多个Set-Cookie,需要按照cookie格式合并后调用该接口。
参数说明:
cookie:String类型,设置请求response中全部cookie。
body:String类型,设置请求response中全部body。
返回值:int类型,返回决策结果,0表示通过,1表示二次校验。
示例代码:
创建滑块。
根据cptCheck返回结果决定是否要创建一个滑块对象,TTCptcha对象提供show和dismiss方法,对应显示滑块和隐藏滑块窗口。TTOption封装了滑块可配置的参数,TTListener包含了滑块的三种回调状态。如果需要自定义滑块窗口页面需要传入自定义页面地址,支持本地 html文件,或者远程页面。
参数说明:
view:View类型,设置当前页面view。
option:TTOption类型,设置滑块配置参数。
listener:TTDelegate类型,设置滑块状态回调。
返回值:TTCaptcha类型,返回滑块对象。
示例代码:
错误码:验证异常,表示在加载滑块过程中检测到异常情况。验证失败,表示用户滑动结束后检测异常情况。
错误码
描述
1001
输入参数错误。
1002
网络检测异常。
1003
js回调数据异常。
1004
WebView加载异常。
1005
js滑块返回异常。
1100
主动关闭滑块。
/**
* 是否进行二次校验
*
* @param cookie response cookie
* @param body response body
* @return 0:通过 1:二次校验
*/
- (int)cptCheck:(NSString *)cookie body:(NSString *)body;
NSString *cookie = @"key1=value1;kye2=value2;";
NSString *body = "....";
int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
NSLog(@"recheck: %d", recheck);
/**
* 显示滑块验证
*
* @param view 父组件
* @param option 参数
* @param detegate 回调协议
*/
- (TTCaptcha *)cptCreate:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)detegate;
#pragma mark滑块回调协议
@protocol TTDelegate <NSObject>
@required
// 滑块验证成功
- (void)success:(TTCaptcha *)captcha data:(NSString *)data;
// 滑块验证失败
- (void)failed:(TTCaptcha *)captcha code:(NSString *)code;
// 滑块验证异常
- (void)error:(TTCaptcha *)captcha code:(NSInteger)code message:(NSString *)message;
@end
#pragma mark滑块参数
@interface TTOption: NSObject
// 点击取消
@property (nonatomic, assign) BOOL cancelable;
// 隐藏滑块错误码
@property (nonatomic, assign) BOOL hideError;
// 自定义页面
@property (nonatomic, strong) NSString *customUri;
// 语言
@property (nonatomic, strong) NSString *language;
// 二次校验请求trace
@property (nonatomic, strong) NSString *traceId;
// 滑块标题文案,最长20字符
@property (nonatomic, strong) NSString *titleText;
// 滑块描述文案,最长60字符
@property (nonatomic, strong) NSString *descText;
// 滑块颜色,格式例如"#007FFF"
@property (nonatomic, strong) NSString *slideColor;
// 是否隐藏traceId
@property (nonatomic, assign) BOOL hideTraceId;
@end
#pragma mark滑块对象
@interface TTCaptcha : NSObject
- (instancetype)init:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)delegate;
// 获取滑块traceId,用于数据统计
- (NSString *)getTraceId;
// 显示滑块
- (void)show;
// 隐藏滑块
- (void)dismiss;
@end
#pragma mark - TTDelegate
- (void)error:(TTCaptcha *)captcha code:(NSInteger)code message:(nonnull NSString *)message {
NSLog(@"captcha error: %ld, %@", code, message);
}
- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
NSLog(@"captcha failed: %@", code);
}
- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
NSLog(@"captcha success: %@", data);
[captcha dismiss];
}
TTOption *option = [[TTOption alloc] init];
// option.customUri = @"ali-tt-captcha-demo";
// option.traceId = @"4534534534adf433534534543";
option.titleText = @"测试 Title";
option.descText = @"测试 Description";
option.language = @"cn";
option.cancelable = true;
option.hideError = true;
option.slideColor = @"#007FFF";
option.hideTraceId= true;
TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self];
[captcha show];
最佳实践示例
#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 {
NSLog(@"captcha flow");
NSThread *thread = [[NSThread alloc] initWithBlock:^{
// 初始化
int code = [[AliTigerTally sharedInstance] initialize:kAppkey];
NSLog(@"tigertally init: %d", code);
// 不能立即同步调用
[NSThread sleepForTimeInterval:2.0];
// 签名
NSString *body = @"hello world";
NSString *whash = nil, *wtoken = nil;
// 自定义加签
whash = [[AliTigerTally sharedInstance] vmpHash:TTTypePost 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 callback:^(NSInteger code, NSString * _Nonnull cookie, NSString * _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";
// option.traceId = "4534534534adf433534534543";
option.titleText = @"测试 Title";
option.descText = @"测试 Description";
option.language = @"cn";
option.cancelable = true;
option.hideError = true;
option.slideColor = @"#007FFF";
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:(NSString *)body callback:(void(^)(NSInteger code, NSString* cookie, NSString *body))callback {
NSLog(@"start reqeust post");
NSURL* requestUrl = [NSURL URLWithString: url];
NSData* requestBody = [body dataUsingEncoding:NSUTF8StringEncoding];
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]);
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, body);
}];
[dataTask resume];
}
#pragma mark - 块TTDelegate
- (void)error:(TTCaptcha *)captcha code:(NSInteger)code message:(nonnull NSString *)message {
NSLog(@"captcha error: %ld, %@", code, message);
}
- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
NSLog(@"captcha failed: %@", code);
}
- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
NSLog(@"captcha success: %@", data);
[captcha dismiss];
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