全部产品
Search
文档中心

Web Application Firewall:Integrasi SDK untuk Aplikasi Android

更新时间:Aug 28, 2025

Untuk mengonfigurasi aturan anti-perayapan pada aplikasi Android Anda, Anda harus terlebih dahulu mengintegrasikan SDK Perlindungan Aplikasi. Topik ini menjelaskan langkah-langkah integrasi yang diperlukan.

Informasi latar belakang

SDK Perlindungan Aplikasi memastikan komunikasi tepercaya dengan menandatangani semua permintaan yang berasal dari aplikasi Anda. Server Web Application Firewall (WAF) kemudian memverifikasi tanda tangan tersebut. Proses ini memungkinkan WAF mengidentifikasi dan memblokir permintaan jahat dari bot dan penyerang lainnya, sehingga melindungi layanan aplikasi Anda.

Batasan

  • SDK mendukung arsitektur arm64-v8a dan armeabi-v7a untuk sistem operasi Android.

  • Tingkat API untuk sistem operasi Android harus 16 atau lebih tinggi.

  • Metode init melakukan operasi yang memakan waktu. Untuk memastikan fitur keamanan sepenuhnya diinisialisasi, tunggu setidaknya 2 detik setelah memanggil init sebelum memanggil metode vmpSign. Ini adalah penundaan yang direkomendasikan untuk memaksimalkan perlindungan dan dapat disesuaikan sesuai kebutuhan. Namun, penundaan yang lebih singkat mungkin mencegah kemampuan keamanan berfungsi sepenuhnya.

  • Saat menggunakan ProGuard untuk pengaburan kode, gunakan opsi -keep untuk mempertahankan metode API SDK. Contohnya:

-keep class com.aliyun.TigerTally.** {*;}
-keep class com.aliyun.captcha.* {*;}
-keepclassmembers,allowobfuscation class * {
     @com.alibaba.fastjson.annotation.JSONField <fields>;
}
-keep class com.alibaba.fastjson.** {*;}

Prasyarat

  • Anda telah memperoleh SDK untuk aplikasi Android Anda.

    Untuk mendapatkan SDK, ajukan tiket.

    SDK untuk aplikasi Android mencakup dua file AAR: AliTigerTally_X.Y.Z.aar dan AliCaptcha_X.Y.Z.aar, di mana X.Y.Z menunjukkan nomor versi.

  • Anda telah memperoleh kunci otentikasi SDK (appkey).

    Setelah mengaktifkan Manajemen BOT, buka halaman Bot Management > App Protection. Dalam daftar aplikasi, klik Obtain and Copy AppKey untuk mendapatkan kunci otentikasi SDK. Kunci ini diperlukan untuk permintaan inisialisasi SDK dan harus disertakan dalam kode integrasi.

    image

    Catatan

    Setiap Akun Alibaba Cloud memiliki appkey unik yang berlaku untuk semua nama domain yang dilindungi oleh WAF. Appkey ini digunakan untuk integrasi SDK di aplikasi Android, iOS, dan Harmony.

    Contoh kunci otentikasi: ****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.

Langkah 1: Buat proyek

Gunakan Android Studio sebagai contoh.

Buat proyek Android dengan mengikuti panduan konfigurasi. Direktori proyek ditampilkan pada gambar berikut.

image.png

Langkah 2: Integrasi paket AAR

  1. Ekstrak paket tigertally-X.Y.Z-xxxxxx-android.tgz. Salin semua file AAR dari folder yang dihasilkan ke direktori libs modul utama Anda (perhatikan bahwa jalur spesifik mungkin bervariasi berdasarkan konfigurasi proyek Anda).image.png

  2. Buka file build.gradle aplikasi Anda. Tambahkan direktori libs sebagai sumber dependensi dan tambahkan dependensi kompilasi untuk AliTigerTally_X.Y.Z.aar dan AliCaptcha_X.Y.Z.aar.

    Penting

    Anda harus mengganti nomor versi X.Y.Z dalam nama file AliTigerTally_X.Y.Z.aar dan AliCaptcha_X.Y.Z.aar dengan yang sebenarnya.

    Konfigurasinya ditunjukkan sebagai berikut:

    dependencies {
        // ...
        implementation files('libs/AliTigerTally_X.Y.Z.aar')
        implementation files('libs/AliCaptcha_X.Y.Z.aar')
      
        // Dependensi pustaka pihak ketiga
        implementation 'com.alibaba:fastjson:1.2.83_noneautotype'
        implementation 'com.squareup.okhttp3:okhttp:3.11.0'
        implementation 'com.squareup.okio:okio:1.14.0
    }

Langkah 3: Filter arsitektur CPU SO

Jika proyek Anda belum menggunakan file SO sebelumnya, tambahkan konfigurasi berikut di file build.gradle Anda.

android {
    defaultConfig {
        ndk {
            abiFilters 'arm64-v8a', 'armeabi-v7a'
        }
    }
}

Langkah 4: Minta izin untuk aplikasi

  • Izin yang Diperlukan

    <uses-permission android:name="android.permission.INTERNET"/>
  • Izin Opsional

    <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"/>
Catatan

Untuk Android 6.0 dan yang lebih baru, Anda harus meminta izin android.permission.READ_EXTERNAL_STORAGE dan android.permission.WRITE_EXTERNAL_STORAGE secara dinamis.

Langkah 5: Tambahkan kode integrasi

Tambahkan file header

import com.alibaba.fastjson.*;
import com.aliyun.tigertally.*;

Atur penandatanganan data

  1. Atur ID pengguna akhir kustom untuk bisnis Anda.

    Ini memungkinkan Anda mengonfigurasi kebijakan mitigasi WAF secara fleksibel.

    /**
    * Atur akun pengguna
    *
    * @param account Akun
    * @return Kode kesalahan
    */
    public static int setAccount(String account)
    • Parameter:

      account: String yang mengidentifikasi pengguna. Kami merekomendasikan menggunakan format yang tidak sensitif.

    • Nilai Balik: Mengembalikan 0 jika pengaturan berhasil, atau -1 jika gagal. Tipe data: int.

    • Kode Contoh:

      // Untuk tamu, Anda dapat melewati setAccount dan langsung menginisialisasi SDK. Setelah masuk, panggil setAccount dan inisialisasi ulang.
      String account = "user001";
      TigerTallyAPI.setAccount(account);
  2. Inisialisasi SDK dan lakukan pengumpulan data.

    Proses inisialisasi mengumpulkan informasi perangkat sekali. Anda dapat memanggil metode init lagi untuk memulai proses pengumpulan baru berdasarkan persyaratan bisnis yang berbeda. Ada tiga mode untuk inisialisasi: pengumpulan penuh, pengumpulan privasi kustom, dan pengumpulan non-privasi. Mode non-privasi tidak mengumpulkan bidang yang melibatkan privasi pengguna, seperti imei, imsi, simSerial, wifiMac, wifiList, bluetoothMac, dan androidId.

    Catatan

    Kami merekomendasikan Anda memilih mode pengumpulan yang sesuai dengan persyaratan kepatuhan internal Anda dan memastikan integritas data. Data lengkap membantu mengidentifikasi risiko potensial secara lebih efektif.

    // Callback inisialisasi
    public interface TTInitListener {
        // code menunjukkan kode status pemanggilan antarmuka
        void onInitFinish(int code);
    }
    
    /**
     * Inisialisasi SDK dengan callback
     *
     * @param appkey Appkey
     * @param type Jenis data yang dikumpulkan
     * @param otherOptions Berbagai opsi parameter
     * @return Kode kesalahan
     */
    public static int init(Context context, String appkey, int collectType,
                           Map<String, String> otherOptions, TTInitListener listener);
    • Parameter:

      • context: Konteks aplikasi Anda. Tipe data: Context.

      • appkey: Appkey SDK Anda. Tipe data: String.

      • collectType: Mode pengumpulan data. Tipe data: int. Nilai valid:

        Nama bidang

        Deskripsi

        Contoh

        TT_DEFAULT

        Mengumpulkan semua data.

        TigerTallyAPI.TT_DEFAULT

        TT_NO_BASIC_DATA

        Tidak mengumpulkan data perangkat dasar. Termasuk: nama perangkat (Build.DEVICE), nomor versi Android (Build.VERSION#RELEASE), dan resolusi layar.

        TigerTallyAPI.X | TigerTallyAPI.Y

        (Menunjukkan bahwa baik X maupun Y tidak dikumpulkan. X dan Y mewakili nama bidang item tertentu.)

        TT_NO_IDENTIFY_DATA

        Tidak mengumpulkan data pengenal perangkat. Termasuk: IMEI, IMSI, SimSerial, BuildSerial (SN), dan alamat MAC.

        TT_NO_UNIQUE_DATA

        Tidak mengumpulkan data pengenal unik. Termasuk: OAID, Google Advertising ID, dan Android ID.

        TT_NO_EXTRA_DATA

        Tidak mengumpulkan data perangkat tambahan. Termasuk: daftar aplikasi jahat/abu-abu, IP LAN, IP DNS, informasi Wi-Fi yang terhubung (SSID, BSSID), daftar Wi-Fi terdekat, informasi lokasi, dan informasi sensor.

        TT_NOT_GRANTED

        Tidak mengumpulkan data privasi apa pun yang tercantum di atas.

        TigerTallyAPI.TT_NOT_GRANTED

      • otherOptions: Opsional. Tipe data: Map<String,String>. Nilai default: null. Nilai valid:

        Nama bidang

        Deskripsi

        Contoh

        IPv6

        Menentukan apakah akan menggunakan nama domain IPv6 untuk melaporkan informasi perangkat. Nilai valid:

        • 0 (default): Gunakan nama domain IPv4.

        • 1: Gunakan nama domain IPv6.

        1

        Intl

        Menentukan wilayah tempat informasi perangkat dilaporkan. Nilai valid:

        • 0 (default): Daratan Tiongkok

        • 1: Luar daratan Tiongkok

        Atur ini ke 1 untuk instans WAF di luar daratan Tiongkok; jika tidak, gunakan nilai default 0.

        1

        CustomUrl

        Menetapkan nama domain server pelaporan data.

        https://cloudauth-device.us-west-1.aliyuncs.com

        CustomHost

        Menetapkan host server pelaporan data.

        cloudauth-device.us-west-1.aliyuncs.com

        Catatan

        Untuk situs internasional umum, cukup menyetel parameter Intl. Parameter CustomUrl dan CustomHost hanya diperlukan saat melaporkan ke situs tertentu, seperti situs AS (Barat): https://cloudauth-device.us-west-1.aliyuncs.com.

      • listener: Antarmuka callback inisialisasi SDK. Tipe data: TTInitListener. Anda dapat memeriksa status hasil inisialisasi spesifik dalam callback. Nilai default: null.

        TTCode

        Kode

        Catatan

        TT_SUCCESS

        0

        SDK diinisialisasi.

        TT_NOT_INIT

        -1

        SDK tidak diinisialisasi.

        TT_NOT_PERMISSION

        -2

        Izin Android dasar yang diperlukan tidak sepenuhnya diberikan kepada SDK.

        TT_UNKNOWN_ERROR

        -3

        Terjadi kesalahan sistem yang tidak diketahui.

        TT_NETWORK_ERROR

        -4

        Terjadi kesalahan jaringan.

        TT_NETWORK_ERROR_EMPTY

        -5

        Terjadi kesalahan jaringan. Konten yang dikembalikan adalah string kosong.

        TT_NETWORK_ERROR_INVALID

        -6

        Format nilai yang dikembalikan tidak valid.

        TT_PARSE_SRV_CFG_ERROR

        -7

        Gagal mengurai konfigurasi server.

        TT_NETWORK_RET_CODE_ERROR

        -8

        Gateway gagal mengembalikan nilai.

        TT_APPKEY_EMPTY

        -9

        Appkey kosong.

        TT_PARAMS_ERROR

        -10

        Terjadi kesalahan parameter lainnya.

        TT_FGKEY_ERROR

        -11

        Terjadi kesalahan perhitungan kunci.

        TT_APPKEY_ERROR

        -12

        Versi SDK tidak sesuai dengan versi appkey.

    • Nilai Balik: Mengembalikan 0 jika inisialisasi berhasil, atau -1 jika gagal. Tipe data: int.

    • Kode Contoh:

      // Appkey mewakili kunci otentikasi yang ditetapkan oleh platform pelanggan Alibaba Cloud.
      final String appkey="******";
      // Parameter opsional. Anda dapat mengonfigurasi IPv6 dan pelaporan internasional.
      Map<String, String> options = new HashMap<>();
      options.put("IPv6", "0");   // Konfigurasikan sebagai IPv4.
      //options.put("Intl", "0");   // Laporkan ke daratan Tiongkok.
      options.put("Intl", "1"); // Laporkan ke wilayah luar daratan Tiongkok.
      
      // Laporkan ke situs AS (Barat).
      //options.put("CustomUrl", "https://cloudauth-device.us-west-1.aliyuncs.com"); 
      //options.put("CustomHost", "cloudauth-device.us-west-1.aliyuncs.com"); 
      
      // Pengumpulan inisialisasi menunjukkan bahwa informasi perangkat dikumpulkan sekali. Anda dapat memanggil fungsi init lagi untuk menginisialisasi ulang pengumpulan berdasarkan persyaratan bisnis yang berbeda.
      // Pengumpulan data penuh.
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_DEFAULT, options, null);
      
      // Tentukan pengumpulan data privasi. Data privasi yang berbeda dapat digabungkan menggunakan "|".
      int privacyFlag = TigerTallyAPI.TT_NO_BASIC_DATA | TigerTallyAPI.TT_NO_UNIQUE_DATA;
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, privacyFlag, options, null);
      
      // Jangan mengumpulkan bidang privasi.
      int ret = TigerTallyAPI.init(this.getApplicationContext(), appkey, TigerTallyAPI.TT_NOT_GRANTED, options, null);
      Log.d("AliSDK", "ret:" + ret);
  3. Hash data.

    Antarmuka penandatanganan kustom ini menghitung hash dari data input dan mengembalikan string whash yang dihasilkan sebagai tanda tangan kustom. request body harus diteruskan sebagai input untuk permintaan POST, PUT, dan PATCH, sementara URL lengkap harus digunakan sebagai input untuk permintaan GET dan DELETE. Selain itu, string whash perlu ditambahkan ke bidang ali_sign_whash di header permintaan HTTP.

    // Jenis permintaan:
    public enum RequestType { GET, POST, PUT, PATCH, DELETE }
    
    /**
     *  Data hash tanda tangan kustom 
     *
     * @param type Tipe data
     * @param input Data hash
     * @return whash
     */
    public static String vmpHash(RequestType type, byte[] input);
  • Parameter:

    • type: Jenis data. Tipe data: RequestType. Nilai valid:

      • GET: Permintaan GET.

      • POST: Permintaan POST.

      • PUT: Permintaan PUT.

      • PATCH: Permintaan PATCH.

      • DELETE: Permintaan DELETE.

    • input: Data yang akan ditandatangani. Tipe data: byte[].

  • Nilai Balik: String whash. Tipe data: String.

  • Kode Contoh:

    // Permintaan GET
    String url = "https://tigertally.aliyun.com/apptest";
    String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.GET, url.getBytes());
    Log.d("AliSDK", "whash:" + whash);
    
    // Permintaan POST
    String body = "hello world";
    String whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, body.getBytes());
    Log.d("AliSDK", "whash:" + whash);

    Pemanggilan antarmuka ini tidak diperlukan saat menggunakan penandatanganan default. Untuk penandatanganan kustom, antarmuka harus dipanggil untuk verifikasi hash sebelum data ditandatangani.

  1. Tandatangani data.

    Metode ini menggunakan teknologi vmp untuk menandatangani data input dan mengembalikan string wtoken untuk otentikasi permintaan.

    /**
     * Penandatanganan data
     *
     * @param type Jenis tanda tangan
     * @param input Data tanda tangan
     * @return wtoken
     */
    public static String vmpSign(int type, byte[] input);
  • Parameter:

    • type: Jenis tanda tangan data. Nilainya harus 1. Tipe data: int.

    • input: Data yang akan ditandatangani. Ini biasanya seluruh requestbody atau whash untuk penandatanganan kustom. Tipe data: byte[].

  • Nilai Balik: String wtoken. Tipe data: String.

  • Kode Contoh:

    // Konsol dikonfigurasi dengan penandatanganan default, yang berarti penandatanganan kustom tidak dipilih.
    String body = "saya adalah request body, terenkripsi atau tidak!";
    String wtoken = TigerTallyAPI.vmpSign(1, body.getBytes("UTF-8"));
    Log.d("AliSDK", "wToken:" + wtoken);
    
    // Konsol dikonfigurasi dengan penandatanganan kustom.
    // Permintaan GET.
    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);
    
    // Permintaan POST.
    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);
    Penting
    • Saat Anda memanggil vmpHash untuk penandatanganan kustom, parameter input dari metode vmpSign adalah string whash yang dihasilkan. Selain itu, saat Anda mengonfigurasi kebijakan anti-bot berbasis skenario untuk aplikasi, nilai dari bidang Custom Signature Field harus disetel ke ali_sign_whash.

    • Saat Anda memanggil vmpHash untuk menghasilkan whash untuk permintaan GET, pastikan URL input identik dengan URL akhir yang digunakan dalam permintaan jaringan. Perhatikan khususnya pengkodean URL. Beberapa kerangka kerja secara otomatis melakukan pengkodean URL pada karakter Cina atau parameter.

    • Parameter input dari metode vmpHash tidak mendukung string kosong. Jika input adalah URL, jalur atau parameter harus ada.

    • Saat Anda memanggil vmpSign, jika badan permintaan kosong (misalnya, badan permintaan POST atau GET kosong), lewatkan objek null atau nilai byte dari string kosong, seperti "".getBytes("UTF-8").

    • Jika whash atau wtoken adalah salah satu dari string berikut, pengecualian terjadi selama inisialisasi:

      • you must call init first: Menunjukkan bahwa metode init tidak dipanggil.

      • you must input correct data: Menunjukkan bahwa data input tidak benar.

      • you must input correct type: Menunjukkan bahwa tipe input tidak benar.

Lakukan otentikasi dua faktor

  1. Evaluasi hasil.

    Periksa bidang cookie dan body di response untuk menentukan apakah verifikasi sekunder diperlukan. header mungkin berisi beberapa entri Set-Cookie, yang harus digabungkan ke dalam format cookie sebelum memanggil antarmuka ini.

    /**
     * Evaluasi apakah akan melakukan otentikasi dua faktor
     *
     * @param cookie Cookie
     * @param body Body
     * @return 0: Lulus 1: Otentikasi dua faktor
     */
    public static int cptCheck(String cookie, String body)
    • Parameter:

      • cookie: Semua cookie dalam response permintaan. Tipe data: String.

      • body: Seluruh body dalam response permintaan. Tipe data: String.

    • Nilai Balik: Mengembalikan hasil keputusan. 0 menunjukkan bahwa permintaan berhasil. 1 menunjukkan bahwa otentikasi dua faktor diperlukan. Tipe data: int.

    • Kode Contoh:

      String cookie = "key1=value1;kye2=value2;";
      String body = "....";
      int recheck = TigerTallyAPI.cptCheck(cookie, body);
      Log.d("AliSDK", "recheck:" + recheck);
  2. Buat slider.

    Buat objek slider berdasarkan hasil yang dikembalikan oleh cptCheck. Objek TTCaptcha menyediakan metode show dan dismiss, yang masing-masing menampilkan dan menyembunyikan jendela slider. TTOption merangkum parameter yang dapat dikonfigurasi untuk slider. TTListener berisi dua status callback untuk slider. Jika Anda memerlukan jendela slider kustom, lewatkan alamat halaman kustom. Baik file HTML lokal maupun halaman jarak jauh didukung.

    /**
     * Buat objek slider
     *
     * @param activity Aktivitas halaman saat ini
     * @param option Parameter
     * @param listener Callback
     * @return Objek verifikasi slider
     */
    public static TTCaptcha cptCreate(Activity activity, TTOption option, TTListener listener);
    
    
    /**
     * Objek slider
     */
    public class TTCaptcha {
        /**
         * Tampilkan slider
         */
        public void show();
    
        /**
         * Sembunyikan slider
         */
        public void dismiss();
    
        /**
         * Dapatkan traceId slider untuk statistik data
         */
        public String getTraceId();
    }
    
    /**
     * Parameter slider
     */
    public static class TTOption {
        // Menentukan apakah akan menyembunyikan slider dengan mengklik area kosong.
        public boolean cancelable;
    
        // Halaman kustom. File HTML lokal dan URL jarak jauh didukung.
        public String customUri;
    
        // Setel bahasa
        public String language;
    }
    
    /**
     * Callback slider
     */
    public interface TTListener {
        /**
         * Verifikasi berhasil
         *
         * @param captcha Objek slider
         * @param data token, default certifyId
         */
        void success(TTCaptcha captcha, String data);
    
        /**
         * Verifikasi gagal
         *
         * @param captcha Objek slider
         * @param code Kode kesalahan
         */
        void failed(TTCaptcha captcha, String code);
    }
    • Parameter:

      • activity: Aktivitas halaman saat ini. Tipe data: Activity.

      • option: Parameter konfigurasi slider. Tipe data: TTOption.

      • listener: Status callback slider. Tipe data: TTListener.

    • Nilai Balik: Objek slider. Tipe data: TTCaptcha.

    • Kode Contoh:

    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();
    Catatan

    Otentikasi gagal menunjukkan bahwa pengecualian terdeteksi setelah pengguna selesai menggeser.

    Daftar berikut menjelaskan kode kesalahan:

    • 1001: Verifikasi gagal.

    • 1002: Pengecualian sistem.

    • 1003: Kesalahan parameter.

    • 1005: Verifikasi dibatalkan.

    • 8001: Kesalahan pemanggilan slider.

    • 8002: Data verifikasi slider abnormal.

    • 8003: Pengecualian internal verifikasi slider.

    • 8004: Kesalahan jaringan.

Contoh praktik terbaik

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(() -> {
            // Inisialisasi.
            Map<String, String> options = new HashMap<>();
            //options.put("Intl", "1"); // Konfigurasikan untuk pelaporan internasional.
            // Pengumpulan data penuh.
            int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_DEFAULT, options, null);
            // Jangan mengumpulkan bidang privasi.
            // int ret = TigerTallyAPI.init(this, APP_KEY, TigerTallyAPI.TT_NOT_GRANTED, null, null);
            Log.d(TAG, "tiger tally init: " + ret);

            // Jangan panggil secara sinkron segera.
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }


            //  Tandatangani.
            String data = "hello world";
            String whash = null, wtoken = null;
            // Penandatanganan kustom.
            whash = TigerTallyAPI.vmpHash(TigerTallyAPI.RequestType.POST, data.getBytes());
            wtoken = TigerTallyAPI.vmpSign(1, whash.getBytes());
            Log.d(TAG, "tiger tally vmp: " + whash + ", " + wtoken);

            // Penandatanganan normal.
            // wtoken = TigerTallyAPI.vmpSign(1, data.getBytes());
            // Log.d(TAG, "tiger tally vmp: " + wtoken);


            // Antarmuka permintaan
            doPost(APP_URL, APP_HOST, whash, wtoken, data, (code, cookie, body) -> {
                // Tentukan apakah akan menampilkan slider
                int recheck = TigerTallyAPI.cptCheck(cookie, body);
                Log.d(TAG, "captcha check result: " + recheck);

                if (recheck == 0) return;
                this.runOnUiThread(this::doShow);
            });
        }).start();
    }

    // Tampilkan 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.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();
    }

    // Kirim permintaan.
    public static void doPost(String url, String host, String whash, String wtoken, String body, Callback callback) {
        Log.d(TAG, "mulai permintaan 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, "sukses: " + response.code() + ", " + response.message());
            } else {
                Log.e(TAG, "gagal: " + 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);
    }
}