アプリにアンチクローラールールを設定する前に、anti-Bot SDKをAndroidアプリに統合する必要があります。 このトピックでは、Anti-Bot SDKをAndroidアプリに統合する方法について説明します。 このトピックでは、Anti-Bot SDKをSDKと呼びます。
背景情報
SDKは、アプリによって送信されるリクエストに署名するために使用されます。 Web Application Firewall (WAF) は、リクエストのシグネチャを検証して、アプリサービスのリスクを特定し、悪意のあるリクエストをブロックします。
制限事項
Androidアプリはarm64-v8aとarmeabi-v7aをサポートする必要があります。
AndroidアプリはAndroid 16以降を使用する必要があります。
SDKの初期化プロセスが完了するまでに時間がかかります。 タスクが完了する前にvmpSign関数を呼び出すことはできません。 SDKの初期化とvmpSignの呼び出しの間に少なくとも2秒の時間差があることを確認します。
proguardを使用してコードを難読化する場合、-keepを使用してSDKの機能を設定できます。
-keep class com.aliyun.TigerTally.** {*;}
前提条件
Androidアプリ用のSDKが取得されました。
SDKを取得するには、
テクニカルサポートのための切符。説明Android用SDKには、次の形式で名前が付けられたAARファイルが含まれています: AliTigerTally_X.Y.Z.aar。 X.Y.Zはファイルのバージョン番号です。
SDK認証キー (appkeyとも呼ばれます) を取得します。
SDK認証キーを取得するには、ボット管理モジュールを有効にし、アプリのシナリオ固有の保護テンプレートを作成します。 [シナリオの設定] ステップで、[App SDK Integration] パラメーターの [AppKeyの取得とコピー] をクリックします。 SDK認証キーを使用してSDK初期化リクエストを送信することもできます。 キーは統合コードに含まれている必要があります。
説明各Alibaba Cloudアカウントは一意のappkeyを使用します。 appkeyは、WAFインスタンスのすべての保護ドメイン名に使用できます。 appkeyを使用して、SDKをAndroidアプリまたはiOSアプリに統合できます。
サンプルSDK認証キー:
**** OpKLvM6zliu6KopyHIhmneb_**** u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_**** vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_**** Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK ****
ステップ1: プロジェクトの作成
この例では、Android Studioを使用してAndroidプロジェクトを作成します。 次の図は、テストプロジェクトを示しています。
手順2: AARファイルをプロジェクトにコピーする
AliTigerTally_X.Y.Z.aar SDKファイルを /project/app/libsディレクトリにドラッグします。
build.gradleファイルを開き、依存関係のソースとしてlibsディレクトリを追加してから、AliTigerTally_X.Y.Z.aarコンパイル依存関係を追加します。
次の表に、設定する必要があるパラメーターを示します。
次のコードのバージョン番号X.Y.Zを、コピーしたAARファイルのバージョン番号に置き換えます。
//...
repositories {
flatDir {
dirs 'libs'
}
}
dependencies {
// ...
compile(name: 'AliTigerTally_X.Y.Z', ext: 'aar')
}
ステップ3: サポートされているアーキテクチャのフィルタリング
場合は ファイルがプロジェクトに存在しない場合は、build.gradleファイルに次の設定を追加します。
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
}
ステップ4: Androidアプリに必要な権限を取得
必要な権限
<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 6.0以降のアプリのandroid.permission.READ_EXTERNAL_STORAGEおよびAndroid. permission.WRITE_EXTERNAL_STORAGE権限を取得する必要があります。
ステップ5: 統合コードの追加
リクエスト署名を作成します。
リクエストに含まれるユーザーIDを指定します。 これにより、WAF保護ポリシーをより効率的に構成できます。
/** * Specify information about the user account. * * @param account The user account. * @return The error code. */ public static int setAccount(String account)
パラメーターの説明
account: ユーザーIDを指定します。 データ型: 文字列。 マスクされたユーザーIDを入力することを推奨します。
戻り値: 設定が成功したかどうかを示す値。 データ型: 整数。 値0は、設定が成功したことを示します。 値-1は、設定が失敗したことを示します。
サンプルコード:
// 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);
SDKを初期化し、1回限りの情報収集を実行します。
端末情報を再度収集する場合は、initialize関数を再度呼び出します。
1回限りの情報収集は、フルデータ収集と非機密フィールド収集のモードをサポートします。 機密フィールドには、ユーザーのimei、imsi、simSerial、wifiMac、wifiList、bluetoothMac、およびandroidIdが含まれます。 機密フィールドを収集する権限が必要です。
説明ユーザーがアプリのプライバシーポリシーに同意する前に、2番目のモードを使用することをお勧めします。 ユーザーがアプリのプライバシーポリシーに同意した後、最初のモードを使用することをお勧めします。 完全なデータ収集は、リスクの特定に役立ちます。
// 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);
パラメーターの説明
context: アプリに渡されるコンテキストを指定します。 データ型: コンテキスト。
appkey: SDK認証キーを指定します。 データ型: 文字列。
type: 収集モードを指定します。 データ型: CollectType。 有効な値:
DEFAULT: 完全なデータ収集。
NO_GRANTED: 機密性のないフィールドコレクション。
otherOptions: データ収集のオプションパラメーターを指定します。 デフォルトでは、このパラメーターはnullに設定されています。 データ型: Map<String, String> 。 オプションのパラメーターは次のとおりです。
パラメーター
説明
例
IPv6
IPv6ドメイン名を使用してデバイス情報を報告するかどうかを指定します。 有効な値:
0: IPv4ドメイン名を使用します。 デフォルト値。
1: IPv6ドメイン名を使用します。
1
Intl
国際ドメイン名を使用してデバイス情報を報告するかどうかを指定します。
有効な値:
0: 中国本土ドメイン名を使用します。 デフォルト値。
1: 国際ドメイン名を使用します。
1
listener: 特定のSDK初期化コールバックインタフェース。
コールバックで返されたコードに基づいて、初期化結果のステータスを確認できます。 デフォルトでは、このパラメーターはnullに設定されています。 データ型: TTInitListener。 次の表で、コードの説明を確認できます。
TTCode
コード
説明
TT_SUCCESS
0
SDKの初期化に成功しました。
TT_NOT_INIT
-1
SDKは初期化されません。
TT_NOT_PERMISSION
-2
SDKに必要なAndroid権限が完全に付与されていないため、SDKの初期化に失敗しました。
TT_UNKNOWN_ERROR
-3
不明なエラーが原因でSDKの初期化に失敗しました。
TT_NETWORK_ERROR
-4
一部のネットワークエラーにより、SDKの初期化に失敗しました。
TT_NETWORK_ERROR_EMPTY
-5
一部のネットワークエラーによりSDKの初期化に失敗し、空の文字列が返されました。
TT_NETWORK_ERROR_INVALID
-6
サーバーから受信したデータ形式が不正なため、SDKの初期化に失敗しました。
TT_PARSE_SRV_CFG_ERROR
-7
サーバー設定の解析に失敗したため、SDKの初期化に失敗しました。
TT_NETWORK_RET_CODE_ERROR
-8
ゲートウェイからのデータ検証が失敗したため、SDKの初期化に失敗しました。
TT_APPKEY_EMPTY
-9
appkeyが空のため、SDKの初期化に失敗しました。
TT_PARAMS_ERROR
-10
一部のパラメーターが正しくないため、SDKの初期化に失敗しました。
TT_FGKEY_ERROR
-11
暗号化アルゴリズムでのキー計算が正しくないため、SDKの初期化に失敗しました。
TT_APPKEY_ERROR
-12
SDKのバージョンとappkeyのバージョンが一致しないため、SDKの初期化に失敗しました。
SDKの初期化の問題を解決できない場合は、
テクニカルサポートのための切符。
戻り値: 初期化が成功したかどうかを示す値。 データ型: 整数。 値0は、初期化が成功したことを示す。 値-1は、初期化が失敗したことを示します。
サンプルコード:
//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);
リクエストに署名します。
vmpSign関数を呼び出してリクエストに署名します。 リクエスト認証のためにwtoken文字列が返されます。
/** * 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);
パラメーターの説明
type: 署名のタイプを指定します。 データ型: CollectType。 値を1に設定します。
input: 署名するデータを指定します。 データ型: byte[] 配列。 入力データは、リクエストボディ全体を指します。
戻り値: wtoken文字列。
サンプルコード:
//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);
署名するリクエストをハッシュします。
署名するリクエストをハッシュします。 whash文字列が生成され、返されます。 リクエストがPOST、PUT、またはPATCHリクエストの場合は、リクエスト本文を指定します。 リクエストがGETまたはDELETEリクエストの場合は、リクエストされたURLを指定します。 HTTPリクエストヘッダーのali_sign_whashフィールドにwhash文字列を追加します。
// 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);
パラメーターの説明
type: データ型を指定します。 データ型: RequestType。 有効な値:
GET: GETリクエスト。
POST: POSTリクエスト。
PUT: PUTリクエスト。
PATCH: PATCHリクエスト。
DELETE: DELETEリクエスト。
input: 署名するデータを指定します。 データ型: byte[] 配列。
戻り値: whash文字列。
サンプルコード:
// 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);
説明vmpSign関数の入力パラメーターの値がvmpHash関数によって返されるwhash文字列である場合、アプリのアンチクローラールールを設定するときにHTTPヘッダーフィールドをali_sign_whashに設定します。
vmpHash関数を呼び出してtypeパラメーターをGETに設定するときは、入力URLがGETリクエストのURLであることを確認してください。 GETリクエストのURLが文字列にエンコードされている場合は、vmpHash関数のinputパラメーターにURLエンコードされた文字列を指定します。
vmpHash関数の入力パラメーターは、空のバイトまたは空の文字列をサポートしていません。 vmpHash関数のdataパラメーターにURLを指定する場合は、URLにパスまたはクエリパラメーターも指定する必要があります。
vmpSign関数を呼び出すときは、入力パラメーターをnullに設定するか、リクエスト本文が空の場合は空の文字列のBytes値に設定します。 空の文字列のバイト値を取得するには、
".getBytes(" UTF-8 ")
コマンドを実行します。次のwhashまたはwtoken文字列のいずれかが返された場合、初期化中に例外が発生しました。
最初にinitを呼び出す必要があります。initialize関数は呼び出されません。
正しいデータを入力する必要があります: 入力データが無効です。
入力する必要があります正しい型: 入力データの型が無効です。
2要素認証を設定します。
2要素認証を実行するかどうかを判断します。
レスポンスのcookieフィールドとbodyフィールドの値に基づいて、2要素認証を実行するかどうかを決定します。 ヘッダ内に複数のSet − Cookieヘッダフィールドが存在してもよい。
/** * 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)
パラメーターの説明
cookie: レスポンス内のすべてのcookieを指定します。 データ型: 文字列。
body: レスポンス内のすべてのボディを指定します。 データ型: 文字列。
戻り値: 値0は、2要素認証が不要であることを示します。 値1は、2要素認証が必要であることを示します。 データ型: 整数。
サンプルコード:
String cookie = "key1=value1;kye2=value2;"; String body = "...."; int recheck = TigerTallyAPI.cptCheck(cookie, body); Log.d("AliSDK", "recheck:" + recheck);
スライダーを作成します。
cptCheckによって返される結果に基づいて、スライダを作成するかどうかを決定します。 TTCaptchaオブジェクトは、スライダーウィンドウを表示または非表示にするShowメソッドとDismissメソッドを提供します。 TTOptionは、設定できるスライダーのパラメーターをカプセル化します。 TTDelegateは、スライダーCAPTCHA検証の3つの状態のコールバック関数をカプセル化します。 カスタムスライダーページを指定する場合は、TTOptionでカスタムページのアドレスを指定します。 オンプレミスHTMLファイルまたは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); }
パラメーターの説明
activity: ページのアクティビティを指定します。 データ型: アクティビティ。
option: スライダー設定パラメーターを指定します。 データ型: TTOption。
listener: スライダーCAPTCHA検証の3つの状態のコールバック関数を指定します。 データ型: TTlistener。
戻り値: スライダー。 データ型: TTCaptcha.
サンプルコード:
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();
説明認証が異常な場合、スライダーの読み込みに失敗しました。 認証が失敗した場合、要求は検証に失敗しました。
エラーコードの意味は、次のとおりです。
1001: 入力パラメーターが無効です。
1002: ネットワーク例外が発生しました。
1003: JavaScriptコールバックデータが異常です。
1004: WebViewはWebページの読み込みに失敗します。
1005: 返されたデータが異常です。
1100: スライダーウィンドウがユーザーによって閉じられます。
例
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);
}
}