您需要在應用中集成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初始化接口存在耗時操作,調用后不能立即同步調用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返回結果決定是否要創建一個滑塊對象,TTCaptcha對象提供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