Before you can configure anti-crawler rules for your apps, you must integrate the Anti-Bot SDK into your Android apps. This topic describes how to integrate the Anti-Bot SDK into Android apps. In this topic, the Anti-Bot SDK is referred to as the SDK.
Background information
The SDK is used to sign requests that are sent by apps. Web Application Firewall (WAF) verifies the signatures in the requests to identify risks in app services and block malicious requests.
Limitations
Your Android apps must support the arm64-v8a and armeabi-v7a.
Your Android apps must use Android 16 or later.
The SDK initialization process requires a period of time to complete. You cannot call the vmpSign function before the task is complete. Ensure there is a time gap of at least 2 seconds between the SDK initialization and the vmpSign calls.
If you use proguard to obfuscate code, you can use -keep to configure the functions of the SDK.
-keep class com.aliyun.TigerTally.** {*;}
Prerequisites
The SDK for Android apps is obtained.
To obtain the SDK, submit a ticket for technical support.
NoteThe SDK for Android contains an AAR file, which is named in the following format: AliTigerTally_X.Y.Z.aar. X.Y.Z is the version number of the file.
The SDK authentication key (also called appkey) is obtained.
To obtain the SDK authentication key, enable the bot management module and create a scenario-specific protection template for apps. In the Configure Scenarios step, click Obtain and Copy AppKey for the App SDK Integration parameter. You can also use the SDK authentication key to send SDK initialization requests. The key must be included in the integration code.
NoteEach Alibaba Cloud account uses a unique appkey. The appkey can be used for all protected domain names of your WAF instance. You can use the appkey to integrate the SDK into Android apps or iOS apps.
Sample SDK authentication key:
****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****
.
Step 1: Create a project
In this example, Android Studio is used to create an Android project. The following figure shows a test project.
Step 2: Copy the AAR file to the project
Drag the AliTigerTally_X.Y.Z.aar SDK file to the /project/app/libs directory.
Open the build.gradle file, add the libs directory as a source of dependencies, and then add the AliTigerTally_X.Y.Z.aar compilation dependency.
The following table describes the parameters that you must configure.
Replace the version number X.Y.Z in the following code with the version number of the AAR file that you copied.
//...
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
// ...
compile(name: 'AliTigerTally_X.Y.Z', ext: 'aar')
}
Step 3: Filter the supported architectures
If .so files do not exist in the project, add the following configurations to the build.gradle file.
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
}
Step 4: Obtain the required permissions for your Android app
Required permissions
<uses-permission android:name="android.permission.INTERNET"/>
Optional permissions
<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"/>
NoteYou must obtain the android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE permissions for Android 6.0 or later apps.
Step 5: Add the integration code
Create request signatures.
Specify a user ID that is included in requests. This way, you can configure WAF protection policies in a more efficient manner.
/** * Specify information about the user account. * * @param account The user account. * @return The error code. */ public static int setAccount(String account)
Parameter description:
account: specifies the user ID. Data type: string. We recommend that you enter a masked user ID.
Returned value: a value that indicates whether the setting is successful. Data type: integer. The value 0 indicates that the setting is successful. The value -1 indicates that the setting failed.
Sample code:
// You do not need to call the setAccount function to specify user IDs for guest users. You can directly call the init function to initialize the SDK. You need to only call the setAccount function for logged on users, and then call the init function. String account = "user001"; TigerTallyAPI.setAccount(account);
Initialize the SDK and perform one-time information collection.
If you want to collect terminal information again, call the initialize function again.
One-time information collection supports the following modes: full data collection and non-sensitive field collection. The sensitive fields include imei, imsi, simSerial, wifiMac, wifiList, bluetoothMac, and androidId of a user. You must have the permissions to collect the sensitive fields.
NoteBefore a user agrees to the privacy policy of the app, we recommend that you use the second mode. After a user agrees to the privacy policy of the app, we recommend that you use the first mode. Full data collection helps identify risks.
// The collection mode: full data collection. The full data collection mode does not collect private data. public enum CollectType { DEFAULT, NOT_GRANTED } // Initialization callback. public interface TTInitListener { // The code indicates the status of the API call. void onInitFinish(int code); } /** * SDK initialization callback. * * @param appkey: The secret key. * @param type: The collection mode. * @param otherOptions: Optional parameters for data collection. * @return code: The status of the initialization result in the callback. For more information,see the table following the listener parameter. */ public static int init(Context context, String appkey, CollectType type, Map<String, String> otherOptions, TTInitListener listener);
Parameter description:
context: specifies the context that is passed to the app. Data type: context.
appkey: specifies the SDK authentication key. Data type: string.
type: specifies the collection mode. Data type: CollectType. Valid values:
DEFAULT: full data collection.
NO_GRANTED: non-sensitive field collection.
otherOptions: specifies optional parameters for data collection. By default, this parameter is set to null. Data type: Map<String, String>. The optional parameters are as follows:
Parameter
Description
Example
IPv6
Specifies whether to report device information using IPv6 domain name. Valid values:
0: use IPv4 domain name. Default value.
1: use IPv6 domain name.
1
Intl
Specifies whether to report device information using international domain name.
Valid values:
0: use the Chinese mainland domain name. Default value.
1: use international domain name.
1
listener: specific SDK initialization callback interaface.
You can check the status of the initialization result based on the code that is returned in the callback. By default, this parameter is set to null. Data type: TTInitListener. You can check the description on the code in the following table:
TTCode
Code
Description
TT_SUCCESS
0
The SDK initialization is successful.
TT_NOT_INIT
-1
The SDK is not initialized.
TT_NOT_PERMISSION
-2
The SDK initialization failed because the required Android permissions for the SDK are not fully granted.
TT_UNKNOWN_ERROR
-3
The SDK initialization failed due to some unknown errors.
TT_NETWORK_ERROR
-4
The SDK initialization failed due to some network errors.
TT_NETWORK_ERROR_EMPTY
-5
The SDK initialization failed due to some network errors, returning an empty string.
TT_NETWORK_ERROR_INVALID
-6
The SDK initialization failed because the data format received from the server is invalid.
TT_PARSE_SRV_CFG_ERROR
-7
The SDK initialization failed because the parsing of the server configurations failed.
TT_NETWORK_RET_CODE_ERROR
-8
The SDK initialization failed because the data validation from the gateway failed.
TT_APPKEY_EMPTY
-9
The SDK initialization failed because the appkey is empty.
TT_PARAMS_ERROR
-10
The SDK initialization failed due to some incorrect parameters.
TT_FGKEY_ERROR
-11
The SDK initialization failed becasue the key calculation in encryption algorithm is incorrect.
TT_APPKEY_ERROR
-12
The SDK initialization failed becasue the SDK version and the appkey version do not match.
If you are unable to resolve the SDK initialization issue, submit a ticket for technical support.
Returned value: a value that indicates whether the initialization is successful. Data type: integer. The value 0 indicates that the initialization is successful. The value -1 indicates that the initialization failed.
Sample code:
//Appkey is the authentication key that is assigned by Alibaba Cloud. final String appkey="******"; // Optional parameters. IPv6 and Intl are configured for this case. Map<String, String> options = new HashMap<>(); options.put("IPv6", "0");// Use IPv4 domain name to report device information. options.put("Intl", "1");// Use international domain name to report device information. // Initialize the SDK and collect terminal information once. If you want to collect terminal information again, call the initialize function. // Specify to collect full data. int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.CollectType.DEFAULT, options, null); //Specify not to collect sensitive parameters. int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.CollectType.NOT_GRANTED, options, null); Log.d("AliSDK", "ret:" + ret);
Sign a request.
Sign a request by calling the vmpSign function. A wtoken string is returned for request authentication.
/** * Sign the request. * * @param type The type of the signature. * @param input The data that you want to sign. * @return wtoken */ public static String vmpSign(int type, byte[] input);
Parameter description:
type: specifies the type of the signature. Data type: CollectType. Set the value to 1.
input: specifies the data that you want to sign. Data type: byte[] array. The input data refers to the entire request body.
Returned value: a wtoken string.
Sample code:
//default signature. String body = "i am the request body, encrypted or not!"; String wtoken = TigerTallyAPI.vmpSign(1, body.getBytes("UTF-8")); Log.d("AliSDK", "wToken:" + wtoken);
Hash the request that you want to sign.
Hash the request that you want to sign. A whash string is generated and returned. If the request is a POST, PUT, or PATCH request, specify the request body. If the request is a GET or DELETE request, specify the requested URL. Add the whash string to the ali_sign_whash field in the HTTP request header.
// Request type: public enum RequestType { GET, POST, PUT, PATCH, DELETE } /** * Hash the request that you want to sign. * * @param type The type of the request data. * @param input The data that you want to sign. * @return whash */ public static String vmpHash(RequestType type, byte[] input);
Parameter description:
type: specifies the data type. Data type: RequestType. Valid values:
GET: GET request.
POST: POST request.
PUT: PUT request.
PATCH: PATCH request.
DELETE: DELETE request.
input: specifies the data that you want to sign. Data type: byte[] array.
Returned value: a whash string.
Sample code:
// GET request String url = "https://tigertally.aliyun.com/apptest"; String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes()); String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes()); Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken); // POST request. String body = "hello world"; String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes()); String wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes()); Log.d("AliSDK", "whash:" + whash + ", wtoken:" + wtoken);
NoteIf the value of the input parameter of the vmpSign function is the whash string that is returned by the vmpHash function, set the HTTP header field to ali_sign_whash when you configure anti-crawler rules for apps.
When you call the vmpHash function and set the type parameter to GET, make sure that the input URL is the URL in the GET request. If the URL in the GET request is encoded into a string, specify the URL-encoded string for the input parameter of the vmpHash function.
The input parameter of the vmpHash function does not support empty bytes or empty strings. If you specify a URL for the data parameter of the vmpHash function, you must also specify the path or query parameters in the URL.
When you call the vmpSign function, set the input parameter to null or the Bytes value of an empty string if the request body is empty. To obtain the Bytes value of an empty string, run the
"".getBytes("UTF-8")
command.If one of the following whash or wtoken strings is returned, an exception occurred during the initialization.
you must call init first: The initialize function is not called.
you must input correct data: The input data is invalid.
you must input correct type: The type of the input data is invalid.
Configure two-factor authentication.
Determine whether to perform two-factor authentication.
Determine whether to perform two-factor authentication based on the values of the cookie and body fields in the response. Multiple Set-Cookie header fields may exist in the header.
/** * Determine whether to perform two-factor authentication. * * @param cookie cookie * @param body body * @return The value 0 indicates that two-factor authentication is not required. The value 1 indicates that two-factor authentication is required. */ public static int cptCheck(String cookie, String body)
Parameter description:
cookie: specifies all cookies in the response. Data type: string.
body: specifies all the body in the response. Data type: string.
Returned value: The value 0 indicates that two-factor authentication is not required. The value 1 indicates that two-factor authentication is required. Data type: integer.
Sample code:
String cookie = "key1=value1;kye2=value2;"; String body = "...."; int recheck = TigerTallyAPI.cptCheck(cookie, body); Log.d("AliSDK", "recheck:" + recheck);
Create a slider.
Determine whether to create a slider based on the result that is returned by cptCheck. The TTCaptcha object provides the Show and Dismiss methods to show or hide the slider window. TTOption encapsulates the parameters of the slider that you can configure. TTDelegate encapsulates the callback functions for the three states of slider CAPTCHA verification. If you want to specify a custom slider page, specify the address of the custom page in TTOption. You can specify an on-premises HTML file or a URL.
/** * Create a slider. * * @param activity Specify whether to show the slider window. * @param option The parameters of the slider. * @param listener The callback functions that you want WAF to call. * @return The slider. */ public static TTCaptcha cptCreate(Activity activity, TTOption option, TTListener listener); /** * The slider. */ public class TTCaptcha { /** * Show the slider. */ public void show(); /** * Hide the slider. */ public void dismiss(); /** * Obtain the slider trace ID for data statistics. */ public String getTraceId(); } /** * The parameters of the slider. */ public static class TTOption { // Specify whether the slider can be hidden by clicking on an empty area. public boolean cancelable; // Specify whether the error codes that are returned can be hidden. public boolean hideError; // Specify the custom page of the slider. An on-premises HTML file or a URL is supported. public String customUri; // Specify the programming language. public String language; // Specify the trace ID of the request that is blocked by WAF. public String traceId; // Specify the title of the slider. The title can be up to 20 characters in length. public String titleText; // Specify the description of the slider. The description can be up to 60 characters in length. public String descText; // Specify color of the slider. Example: "#007FFF". public String slideColor; // Specify whether to hide the trace ID. public boolean hideTraceId; } /** * The callback listener. */ public interface TTListener { /** * The slider CAPTCHA verification is successful. * * @param captcha The slider. * @param data token. The default value is the trace ID of the request that is blocked by WAF. */ void success(TTCaptcha captcha, String data); /** * Slider CAPTCHA verification failed. * * @param captcha The slider. * @param code The error code. */ void failed(TTCaptcha captcha, String code); /** * A slider CAPTCHA verification exception occurred. * * @param captcha The slider. * @param code The error code. * @param message The error message. */ void error(TTCaptcha captcha, int code, String message); }
Parameter description:
activity: specifies the page activity. Data type: activity.
option: specifies the slider configuration parameters. Data type: TTOption.
listener: specifies the callback functions for the three states of slider CAPTCHA verification. Data type: TTlistener.
Returned value: a slider. Data type: TTCaptcha.
Sample code:
TTCaptcha.TTOption option = new TTCaptcha.TTOption(); // option.customUri = "file:///android_asset/ali-tt-captcha-demo.html"; // option.traceId = "4534534534adf433534534543"; option.titleText = "Test Title"; option.descText = "Test Description"; option.language = "cn"; option.cancelable = true; option.hideError = true; option.slideColor = "#007FFF"; option.hideTraceId= true; TTCaptcha captcha = TigerTallyAPI.cptBuild(this, option, new TTCaptcha.TTListener() { @Override public void success(TTCaptcha captcha, String data) { Log.d(TAG, "captcha check success:" + data); captcha.dismiss(); } @Override public void failed(TTCaptcha captcha, String code) { Log.d(TAG, "captcha check failed:" + code); } @Override public void error(TTCaptcha captcha, int code, String message) { Log.d(TAG, "captcha check error, code: " + code + ", message: " + message); } }); captcha.show();
NoteIf the authentication is abnormal, the slider failed to load. If the authentication failed, the request failed the verification.
The error codes are described as follows:
1001: The input parameter is invalid.
1002: A network exception occurred.
1003: The JavaScript callback data is abnormal.
1004: WebView fails to load the webpage.
1005: The returned data is abnormal.
1100: The slider window is closed by the user.
Examples
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(() -> {
// Initialize the SDK.
Map<String, String> options = new HashMap<>();
options.put("Intl", "1");// Use international domain name to report device information.
// Specify to collect full data.
int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.CollectType.DEFAULT, null, null);
// Specify not to collect sensitive fields.
// int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.CollectType.NOT_GRANTED, null, null);
Log.d(TAG, "tiger tally init: " + ret);
// Wait until the initialization of the SDK is complete.
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Sign the request.
String data = "hello world";
String whash = null, wtoken = null;
// Hash and sign the request.
whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, data.getBytes());
wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
Log.d(TAG, "tiger tally vmp: " + whash + ", " + wtoken);
// Sign the request without the need to hash the request.
// wtoken = TigerTallyAPI.vmpSign(1, data.getBytes());
// Log.d(TAG, "tiger tally vmp: " + wtoken);
// Send a request.
doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
// Determine whether to create a slider.
int recheck = TigerTallyAPI.cptCheck(cookie, body);
Log.d(TAG, "captcha check result: " + recheck);
if (recheck == 0) return;
this.runOnUiThread(this::doShow);
});
}).start();
}
// Create a slider.
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.traceId = "4534534534adf433534534543";
option.titleText = "Test Title";
option.descText = "Test Description";
option.language = "cn";
option.cancelable = true;
option.hideError = true;
option.slideColor = "#007FFF";
TTCaptcha captcha = TigerTallyAPI.cptCreate(this, option, new TTCaptcha.TTListener() {
@Override
public void success(TTCaptcha captcha, String data) {
Log.d(TAG, "captcha check success:" + data);
captcha.dismiss();
}
@Override
public void failed(TTCaptcha captcha, String code) {
Log.d(TAG, "captcha check failed:" + code);
}
@Override
public void error(TTCaptcha captcha, int code, String message) {
Log.d(TAG, "captcha check error, code: " + code + ", message: " + message);
}
});
captcha.show();
}
// Send a request.
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);
}
}