您需要在应用中集成SDK,才能在控制台Bots中配置App防爬场景化规则。本文介绍了如何为Android应用集成App防护SDK(以下简称SDK)。
使用限制
-
Android应用支持如下2个软件版本的SO:arm64-v8a、armeabi-v7a。
-
Android应用的API版本必须是16及以上。
-
init初始化接口存在耗时操作,为确保安全能力完整性,建议在调用init接口后,确保至少间隔2秒再调用后续的vmpSign签名接口。此间隔为推荐值(非强制要求),旨在提升SDK的防护效果。实际调用中可根据业务需求灵活调整,但缩短间隔可能影响安全能力的完整生效。
-
当使用proguard进行代码混淆时,请使用-keep选项对SDK的接口函数进行设置,例如:
-keep class com.aliyun.TigerTally.** {*;} -keep class com.aliyun.captcha.* {*;} -keepclassmembers,allowobfuscation class * { @com.alibaba.fastjson.annotation.JSONField <fields>; } -keep class com.alibaba.fastjson.** {*;}
前提条件
已获取Android应用对应的SDK。
获取方法:请提交工单,联系产品技术专家获取SDK。
说明Android应用对应的SDK包含2个AAR文件,文件名为AliTigerTally_X.Y.Z.aar、AliCaptcha_X.Y.Z.aar,其中X.Y.Z表示版本号。
已获取SDK认证密钥(即appkey)。
创建Bots规则集时单击获取并复制appKey,获取SDK认证密钥。该密钥用于发起SDK初始化请求,需要在集成代码中使用。

步骤一:新建工程
以Android Studio工具为例,新建一个Android工程,并按照配置向导完成创建。创建好的工程目录如下图所示。

步骤二:集成AAR包
-
将获取到的SDK文件tigertally-X.Y.Z-xxxxxx-android.tgz包解压,将文件夹中的所有aar文件拷贝到主工程模块下的libs目录中(具体以工程实际配置为准)。

-
打开App的build.gradle文件,将libs目录添加为查找依赖的源,并添加编译依赖为AliTigerTally_X.Y.Z.aar、AliCaptcha_X.Y.Z.aar。
重要您需要将AliTigerTally_X.Y.Z.aar、AliCaptcha_X.Y.Z.aar文件的版本号X.Y.Z替换成您获取的AAR文件的版本号。
具体配置信息如下所示:
dependencies { // ... implementation files('libs/AliTigerTally_X.Y.Z.aar') implementation files('libs/AliCaptcha_X.Y.Z.aar') // 三方库依赖 implementation 'com.alibaba:fastjson:1.2.83_noneautotype' implementation 'com.squareup.okhttp3:okhttp:3.11.0' implementation 'com.squareup.okio:okio:1.14.0 }
步骤三:过滤SO CPU架构
如果项目在此之前未使用过SO,需在build.gradle中添加以下配置。
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
}
步骤四:为应用申请权限
-
必备权限
<uses-permission android:name="android.permission.INTERNET"/> -
可选权限
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.READ_PHONE_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
android.permission.READ_EXTERNAL_STORAGE和android.permission.WRITE_EXTERNAL_STORAGE权限在Android 6.0及以上版本需要动态申请。
步骤五:添加集成代码
1. 添加头文件
IDFA版本配置信息如下:
#import <AliTigerTally_IDFA/AliTigerTally.h>非IDFA版本配置信息如下:
#import <AliTigerTally_NOIDFA/AliTigerTally.h>
2. 设置数据签名
设置您业务中自定义的终端用户标识,方便您更灵活地配置WAF防护策略。
/** * 设置用户账户 * * @param account 账户信息 */ - (void)setAccount:(NSString *)account;参数说明:
account:NSString类型,表示标识一个用户的字符串,建议您使用脱敏后的格式。
返回值:无返回,或返回void。
示例代码:
// 游客身份可以暂时先不setAccount, 直接初始化; 登录以后调用setAccount和重新初始化 [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
初始化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参数即可,只有指定站点上报需要设置CustomUrl和CustomHost,站点列表如下:
-
Intl = 0时,默认为上海站点: https://cloudauth-device.cn-shanghai.aliyuncs.com
-
Intl = 1时:
-
默认为新加坡站点: https://cloudauth-device.ap-southeast-1.aliyuncs.com
-
印度尼西亚(雅加达)站点: https://cloudauth-device.ap-southeast-5.aliyuncs.com
-
美国(硅谷)站点: https://cloudauth-device.us-west-1.aliyuncs.com
-
德国(法兰克福)站点: https://cloudauth-device.eu-central-1.aliyuncs.com
-
中国香港站点:https://cloudauth-device.cn-hongkong.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];
数据哈希。
自定义加签接口,对传入的数据input进行计算,生成一个whash字符串作为自定义签名数据。Post、Put、Patch请求需要传入request body,Get、Delete请求传入完整的URL地址。同时,whash字符串需要添加到HTTP请求header的ali_sign_whash中。
说明使用
vmpHash函数生成自定义签名数据,必须在控制台上配置自定义加签字段为ali_sign_whash(Bot特征识别--开启自定义加签字段--选择字段名为header--值填写为ali_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);
说明控制台配置默认签名(即不勾选自定义加签)不需要调用该接口,勾选自定义加签时需要在数据签名前调用该接口进行哈希校验。
数据签名。
使用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])。
当whash或wtoken为以下字符串时表示初始化流程存在异常:
you must call init first:表示未调用init函数。
you must input correct data:表示传入数据错误。
you must input correct type:表示传入类型错误。
3. 二次校验
判断结果。
根据response中cookie和body字段判断是否要进行二次校验。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);
创建滑块。
根据cptCheck返回结果决定是否要创建一个滑块对象,TTCaptcha对象提供show和dismiss方法,对应显示滑块和隐藏滑块窗口。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:网络错误。
最佳实践示例
package com.aliyun.tigertally.apk;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.aliyun.TigerTally.TigerTallyAPI;
import com.aliyun.TigerTally.captcha.api.TTCaptcha;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class DemoActivity extends AppCompatActivity {
private final static String TAG = "TigerTally-Demo";
private final static String APP_HOST = "******";
private final static String APP_URL = "******";
private final static String APP_KEY = "******";
private final static OkHttpClient okHttpClient = new OkHttpClient();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
doTest();
}
private void doTest() {
Log.d(TAG, "captcha flow");
new Thread(() -> {
// 初始化
Map<String, String> options = new HashMap<>();
//options.put("Intl", "1"); // 配置为国际上报
// 全量采集
int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_DEFAULT, options, null);
// 不采集隐私字段
// int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_NOT_GRANTED, null, null);
Log.d(TAG, "tiger tally init: " + ret);
// 不能立即同步调用
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 签名
String data = "hello world";
String whash = null, wtoken = null;
// 自定义加签
whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, data.getBytes());
wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
Log.d(TAG, "tiger tally vmp: " + whash + ", " + wtoken);
// 正常加签
// wtoken = TigerTallyAPI.vmpSign(1, data.getBytes());
// Log.d(TAG, "tiger tally vmp: " + wtoken);
// 请求接口
doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
// 判断是否需要显示滑块
int recheck = TigerTallyAPI.cptCheck(cookie, body);
Log.d(TAG, "captcha check result: " + recheck);
if (recheck == 0) return;
this.runOnUiThread(this::doShow);
});
}).start();
}
// 显示滑块
public void doShow() {
Log.d(TAG, "captcha show");
TTCaptcha.TTOption option = new TTCaptcha.TTOption();
// option.customUri = "file:///android_asset/ali-tt-captcha-demo.html";
option.language = "cn";
option.cancelable = false;
TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
@Override
public void success(TTCaptcha captcha, String data) {
Log.d(TAG, "captcha check success:" + data);
}
@Override
public void failed(TTCaptcha captcha, String code) {
Log.d(TAG, "captcha check failed:" + code);
}
});
captcha.show();
}
// 发送请求
public static void doPost(String url, String host, String whash, String wtoken, String body, Callback callback) {
Log.d(TAG, "start request post");
int responseCode = 0;
String responseBody = "";
StringBuilder responseCookie = new StringBuilder();
try {
Request.Builder builder = new Request.Builder()
.url(url)
.addHeader("wToken", wtoken)
.addHeader("Host", host)
.post(RequestBody.create(MediaType.parse("text/x-markdown"), body.getBytes()));
if (whash != null) {
builder.addHeader("ali_sign_whash", whash);
}
Response response = okHttpClient.newCall(builder.build()).execute();
responseCode = response.code();
responseBody = response.body() == null ? "" : response.body().string();
for (String item : response.headers("Set-Cookie")) {
responseCookie.append(item).append(";");
}
Log.d(TAG, "response code:" + responseCode);
Log.d(TAG, "response cookie:" + responseCookie);
Log.d(TAG, "response body:" + (responseBody.length() > 100 ? responseBody.substring(0, 100) : ""));
if (response.isSuccessful()) {
Log.d(TAG, "success: " + response.code() + ", " + response.message());
} else {
Log.e(TAG, "failed: " + response.code() + ", " + response.message());
}
response.close();
} catch (Exception e) {
e.printStackTrace();
responseCode = -1;
responseBody = e.toString();
} finally {
if (callback != null) {
callback.onResponse(responseCode, responseCookie.toString(), responseBody);
}
}
}
public interface Callback {
void onResponse(int code, String cookie, String body);
}
}