すべてのプロダクト
Search
ドキュメントセンター

Web Application Firewall:iOS アプリの SDK 統合

最終更新日:Aug 06, 2026

ボット管理 コンソールでアプリ向けのシナリオベースの軽減ポリシーを構成するには、Web Application Firewall (WAF)App Protection SDK を統合する必要があります。このトピックでは、iOS アプリに App Protection SDK を統合する方法について説明します。

背景情報

アプリ保護 SDK は、アプリのクライアントから送信されるリクエストに署名します。次に、WAF サーバーがリクエストの署名を検証して悪意のあるリクエストを識別・ブロックし、アプリを保護します。

制限事項

  • iOS SDK には、広告識別子 (IDFA) 版と非 IDFA 版の 2 つのバージョンがあります。対応する SDK ファイルは次のとおりです:

    • AliTigerTally_IDFA.framework

    • AliTigerTally_NOIDFA.framework

    お使いの iOS プロジェクトで IDFA を使用している場合は、AliTigerTally_IDFA 版 SDK の統合を推奨します。そうでない場合は、AliTigerTally_NOIDFA 版を使用してください。

  • init インターフェイスは時間がかかる場合があります。最適な保護効果を得るため、init インターフェイスを呼び出した後、vmpSign インターフェイスを呼び出す前に、少なくとも 2 秒間待機してください。この遅延は必須ではありませんが、間隔が短いとセキュリティ機能が完全に効果を発揮しない可能性があります。ビジネスニーズに応じて間隔を調整できます。

  • アプリ保護 SDK には、iOS バージョン 9.0 以降が必要です。

前提条件

  • iOS アプリ用の SDK を取得済みであること。

    SDK を取得するには、チケットを送信して、製品技術エキスパートにお問い合わせください。

    説明

    iOS アプリ用の SDK ファイル名は tigertally-X.Y.Z-xxxx-ios.zip です。X.Y.Z はバージョン番号です。このファイルには、フレームワーク ファイルが 2 つと xcframework ファイルが 2 つ含まれています。

  • SDK の 認証キー (別名 appkey) を取得済みであること。

    ボット管理を有効化した後、Web Application Firewall 3.0 コンソールにログインできます。上部メニューで、WAF インスタンスのリソースグループとリージョン ([中国本土] または [中国本土以外]) を選択します。ナビゲーションペインで、保護設定 > BOT 管理 > Bot 管理テンプレート を選択します。アプリ 保護 タブに移動し、右上の AppKey の取得と複製 をクリックして、SDK 認証キーを取得します。このキーは SDK の初期化に必要で、統合コードに含める必要があります。

    説明

    Alibaba Cloud アカウント には、WAF で保護されるすべての ドメイン名 に対して一意の appkey があります。この appkey は、Android、iOS、および HarmonyOS の各アプリでの SDK 統合に使用されます。

    認証キー の例:****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****

ステップ 1:プロジェクトの作成

Xcode で、セットアップウィザードを使用して iOS プロジェクトを作成します。次の例は、プロジェクトのディレクトリ構造です。

tigerdemo
  tigerdemo
    tool
    AppDelegate.h
    AppDelegate.m
    ViewController.h
    ViewController.m
    main.m
    Main.storyboard
    LaunchScreen.storyboard
    AliTigerTallyApp.entitlements
    Info.plist

ステップ 2:フレームワークの統合

SDK ファイル [AliTigerTally_IDFA.framework] または [AliTigerTally_NOIDFA.framework] をプロジェクトに追加します。

  • IDFA 版

    ファイルを追加した後、[AliTigerTally_IDFA.framework][Embed] オプションを [Do Not Embed] に設定します。

  • 非 IDFA 版

    [AliTigerTally_NOIDFA.framework] をプロジェクトの [Frameworks, Libraries, and Embedded Content] セクションに追加し、[Embed][Do Not Embed] に設定します。

Captcha モジュール SDK ファイル [AliCaptcha.framework] をプロジェクトに追加し、その [Embed] オプションを [Do Not Embed] に設定します。

リソースファイル [AliCaptcha.bundle] を、[Build Phases] > [Copy Bundle Resources] に追加します。

ステップ 3:依存ライブラリの追加

お使いの Xcode プロジェクトの [Frameworks, Libraries, and Embedded Content] セクションで、次の依存ライブラリを追加します。

依存ライブラリ

IDFA 利用時に必須

IDFA 非利用時に必須

libc++.tbd

はい

はい

libresolv.9.tbd

はい

はい

CoreTelephony.framework

はい

はい

AdSupport.framework

はい

いいえ

AppTrackingTransparency.framework

はい

いいえ

ステップ 4:リンカフラグの追加

[ビルド設定][他のリンカフラグ]を検索し、-ObjC フラグを追加します。

ステップ 5:統合コードの追加

1. ヘッダーファイルの追加

  • IDFA 版の場合は、次の import 文を追加します。

    #import <AliTigerTally_IDFA/AliTigerTally.h>
  • 非 IDFA 版の場合は、次の import 文を追加します。

    #import <AliTigerTally_NOIDFA/AliTigerTally.h> 

2. データ署名の設定

  1. ビジネスに合わせてカスタムのエンドユーザー識別子を設定します。これにより、WAF 保護ポリシーをより柔軟に設定できます。

    /**
    * ユーザーアカウントを設定します。
    *
    * @param account   アカウント情報。
    */
    - (void)setAccount:(NSString *)account;
    • パラメータ:

      • account:型:NSString。ユーザーを識別する文字列。非機密化された形式を使用することを推奨します。

    • 戻り値:なし。

    • 例:

      // ゲストユーザーの場合、setAccount をスキップして直接初期化できます。ユーザーがログインした後、setAccount を呼び出して再初期化します。
      [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
  2. SDK を初期化してデータ収集を実行します。

    初期化ごとに、デバイス情報が 1 回収集されます。init 関数を再度呼び出して、さまざまなビジネスシナリオに合わせてデータ収集を再初期化できます。

    初期化は、完全、カスタムプライバシー、非プライバシーの 3 つのデータ収集モードをサポートしています。非プライバシーモードでは、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

        基本デバイスデータを収集しません。

        これには、デバイス名、OS バージョン、画面解像度が含まれます。

        TT_NO_X | TT_NO_Y

        (X と Y は除外するデータフィールドの種類を表します)

        TT_NO_UNIQUE_DATA

        一意の識別子データを収集しません。

        これには、IDFV と IDFA が含まれます。

        TT_NO_EXTRA_DATA

        拡張デバイスデータを収集しません。

        これには、接続中の Wi-Fi 情報 (SSID、BSSID) と周辺の Wi-Fi ネットワークのリストが含まれます。

        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

        データ報告サーバーのホストを設定します。

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

        説明

        ほとんどの国際サイトでは、Intl パラメーターのみを設定します。特定のサイトにデータを報告するには、CustomUrl および CustomHost パラメーターを設定します。利用可能なサイトは次のとおりです。

        • Intl0 に設定されている場合、データは中国 (上海) のデフォルトサイトである https://cloudauth-device.cn-shanghai.aliyuncs.com に報告されます

        • Intl1 に設定されている場合:

          • デフォルトサイトはシンガポールです:https://cloudauth-device.ap-southeast-1.aliyuncs.com

          • インドネシア (ジャカルタ):https://cloudauth-device.ap-southeast-5.aliyuncs.com

          • US (Silicon Valley):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

        コード

        説明

        TT_SUCCESS

        0

        初期化に成功しました。

        TT_NOT_INIT

        -1

        SDK が初期化されていません。

        TT_NOT_PERMISSION

        -2

        必要な 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

        アプリキーが空です。

        TT_PARAMS_ERROR

        -10

        その他のパラメータエラーが発生しました。

        TT_FGKEY_ERROR

        -11

        キー計算エラーが発生しました。

        TT_APPKEY_ERROR

        -12

        SDK のバージョンがアプリキーのバージョンと一致しません。

    • 戻り値:型:int。エラーコード0 は成功を示し、負の数は失敗を示します。

    • 例:

      // appkey は、Alibaba Cloud コンソールで取得した認証キーです。
      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 の呼び出しごとに、デバイス情報が 1 回収集されます。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];
  3. データハッシュ。

    このカスタム署名インターフェースは、入力データをハッシュして whash 文字列を生成します。この文字列がカスタム署名として機能します。POST、PUT、および PATCH リクエストの場合は、リクエストボディを入力として使用します。GET および DELETE リクエストの場合は、完全な URL を使用します。生成された whash 文字列を HTTP リクエストヘッダーの ali_sign_whash フィールドに追加する必要があります。

    // リクエストタイプ:
    typedef NS_ENUM(NSInteger, TTRequestType) {
        TT_GET=0, TT_POST, TT_PUT, TT_PATCH, TT_DELETE
    };
    
    /**
     * カスタム署名データをハッシュします。
     * @param type  データ型。
     * @param input 署名するデータ。
     * @return whash 文字列。
     */
    - (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;
    • パラメータ:

      • type:型:TTRequestType。データ型。有効な値:

        • GET:GET リクエストデータ。

        • POST:POST リクエストデータ。

        • PUT:PUT リクエストデータ。

        • PATCH:PATCH リクエストデータ。

        • DELETE:DELETE リクエストデータ。

      • input:型:NSData。署名するデータ。type に応じて、リクエストボディまたは 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);
    説明

    このインターフェースは、WAF コンソールでカスタム署名オプションを有効にしている場合にのみ呼び出す必要があります。デフォルトの署名設定を使用する場合は、このステップをスキップできます。

  4. データ署名。

    このメソッドは VMP テクノロジーを使用し、input データに署名して、リクエスト認証用の wtoken 文字列を返します。

    /**
     * データに署名します。
     * @param input 署名するデータ。
     * @return wtoken 文字列。
     */
    - (NSString *)vmpSign:(NSData *)input;
    • パラメータ:

      • input:型:NSData。署名するデータ。これは通常、リクエストボディ全体またはカスタム署名による 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);
      説明
      • カスタム署名を使用する場合、vmpSign インターフェースの input には vmpHash によって生成された whash 文字列を指定する必要があります。アプリ保護のシナリオベースポリシーを設定する場合、カスタム署名フィールドali_sign_whash を設定する必要があります。

      • vmpHash を呼び出して GET リクエストの whash を生成する場合、入力 URL が最終的なネットワークリクエスト URL と同一であることを確認してください。一部のフレームワークは文字やパラメーターを自動的にエンコードするため、URL エンコーディングには特に注意してください。

      • vmpHash インターフェースの input は空にできません。入力が URL の場合、パスまたはパラメーターを含む必要があります。

      • vmpSign を呼び出す際、リクエストボディが空の場合 (たとえば、GET リクエストやボディのない POST リクエストなど)、nil または [@"" dataUsingEncoding:NSUTF8StringEncoding] のような空の文字列の NSData 表現を渡します。

      • whash または wtoken が次のいずれかの文字列を返す場合、エラーを示します:

        • you must call init first: init 関数が呼び出されていません。

        • you must input correct data: 入力データが無効です。

        • you must input correct type: 入力タイプが無効です。

3. 二次検証

  1. 結果の確認。

    二次検証が必要かどうかを判断するには、レスポンスのクッキーボディを確認します。ヘッダーに複数の Set-Cookie フィールドが含まれている場合は、このインターフェースを呼び出す前に、それらを 1 つのクッキー文字列にマージします。

    /**
     * 二次検証を実行するかどうかを確認します。
     *
     * @param cookie  レスポンスの Cookie。
     * @param body    レスポンスボディ。
     * @return 0:合格、1:二次検証が必要。
     */
    - (int)cptCheck:(NSString *)cookie body:(NSData *)body;
    • パラメータ:

      • cookie:型:NSString。リクエストレスポンス内のすべてのクッキー。

      • body:型:NSData。リクエストレスポンスボディ全体。

    • 戻り値:型:int。判定結果。0 はリクエストが合格したことを示し、1二次検証が必要であることを示します。

      • 例:

      NSString *cookie = @"key1=value1;key2=value2;";
      NSData *body = xxx;
      int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
      NSLog(@"recheck: %d", recheck);
  2. スライダーの作成。

    cptCheck の結果が検証の必要性を示している場合は、スライダーオブジェクトを作成します。TTCaptcha オブジェクトは、スライダーウィンドウを制御するための show および dismiss メソッドを提供します。TTOption を使用してそのパラメータを設定し、TTDelegate を使用してそのステータスコールバックを処理します。スライダーウィンドウをカスタマイズするには、カスタムページへの URL を提供する必要があります。この URL には、ローカルの 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;
    
    // データ分析用のスライダートレース ID を取得します。
    - (NSString *)getTraceId;
    
    // スライダーを表示します。
    - (void)show;
    
    // スライダーを閉じます。
    - (void)dismiss;
    
    @end
    • パラメータ:

      • view:型:View。現在のページビュー。

      • option:型:TTOption。スライダーの設定パラメータ。

      • delegate:型: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:ネットワークエラーが発生しました。

ベストプラクティス

#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];
    // ビューの読み込み後に追加のセットアップを行います。


    [self doTest];
}

- (void)doTest {

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        // SDK を初期化します。
        NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
        // [options setValue:@"1" forKey:@"Intl"];     // 国際レポート用に設定します。
        int code = [[AliTigerTally sharedInstance] init:kAppkey collectType:TT_DEFAULT options:options listener:nil];
        NSLog(@"tigertally init: %d", code);

        // すぐに同期呼び出しを行わないでください。
        [NSThread sleepForTimeInterval:2.0];

        // 署名を生成します。
        NSString *body = @"hello world";
        NSString *whash = nil, *wtoken = nil;
        // カスタム署名。
        whash  = [[AliTigerTally sharedInstance] vmpHash:TT_Post 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 dataUsingEncoding:NSUTF8StringEncoding] callback:^(NSInteger code, NSString * _Nonnull cookie, NSData * _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-ios";
    option.language   = @"cn";
    option.cancelable = true;
    
    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:(NSData *)body callback:(void(^)(NSInteger code, NSString* cookie, NSData *body))callback {
    NSLog(@"start reqeust post");
    
    NSURL* requestUrl = [NSURL URLWithString: url];
    NSData* requestBody = body;
    
    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] dataUsingEncoding:NSUTF8StringEncoding]);
            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, data);
    }];
    [dataTask resume];
}

#pragma mark - TTDelegate
- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
    NSLog(@"CAPTCHA failed: %@", code);

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:code preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:true completion:nil];
}

- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
    NSLog(@"CAPTCHA success: %@", data);

    UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:data preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:true completion:nil];
}

@end