Todos los productos
Search
Centro de documentos

Web Application Firewall:iOS app SDK integration

Última actualización: Sep 21, 2026

Para configurar políticas de mitigación basadas en escenarios para su aplicación en la consola de Bot Management, debe integrar el App Protection SDK de Web Application Firewall (WAF). En este tema se describe cómo integrar el App Protection SDK con una aplicación iOS.

Contexto

El App Protection SDK firma las solicitudes que provienen del cliente de la aplicación. Después, el servidor de WAF verifica la firma de la solicitud para identificar y bloquear peticiones maliciosas. De esta manera, protege su aplicación.

Límites

  • El SDK de iOS está disponible en dos versiones: una con Identifier for Advertising (IDFA) y otra sin IDFA. Los archivos correspondientes del SDK son los siguientes:

    • AliTigerTally_IDFA.framework

    • AliTigerTally_NOIDFA.framework

    Si su proyecto de iOS utiliza IDFA, se recomienda integrar la versión AliTigerTally_IDFA del SDK. De lo contrario, utilice la versión AliTigerTally_NOIDFA.

  • La interfaz init puede consumir bastante tiempo. Para obtener una protección óptima, espere como mínimo 2 segundos después de llamar a la interfaz init antes de invocar la interfaz vmpSign. Este retraso no es obligatorio, pero un intervalo más corto podría impedir que las funciones de seguridad surtan efecto por completo. Ajuste el intervalo según sus necesidades de negocio.

  • El App Protection SDK requiere la versión 9.0 de iOS o posterior.

Requisitos previos

  • Obtuvo el SDK para su aplicación iOS.

    Para obtener el SDK, envíe un ticket para comunicarse con un experto técnico del producto.

    Nota

    El archivo del SDK para una aplicación iOS se llama tigertally-X.Y.Z-xxxx-ios.zip, donde X.Y.Z es el número de versión. El archivo contiene dos archivos framework y dos archivos xcframework.

  • Obtuvo la clave de autenticación del SDK (también conocida como appkey).

    Una vez que habilite Bot Management, podrá iniciar sesión en la consola de Web Application Firewall 3.0. En la barra de menús superior, seleccione el resource group y la región (Chinese Mainland o Outside Chinese Mainland) de la instancia de WAF.. En el panel de navegación izquierdo, elija Protection Config > Bot Management > Bot Management Template. Vaya a la pestaña App Protection y haga clic en Obtain and Copy AppKey en la esquina superior derecha para recuperar la clave de autenticación del SDK. Esta clave es obligatoria para inicializar el SDK y debe incluirse en su código de integración.

    Nota

    Cada cuenta de Alibaba Cloud tiene un appkey único para todos los nombres de dominio protegidos por WAF. Este appkey se utiliza para integrar el SDK en aplicaciones Android, iOS y HarmonyOS.

    Ejemplo de clave de autenticación: ****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.

Paso 1: Crear un proyecto

En Xcode, cree un proyecto de iOS mediante el asistente de configuración. A continuación se muestra la estructura de directorios del proyecto.

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

Paso 2: Integrar el framework

Agregue el archivo del SDK, AliTigerTally_IDFA.framework o AliTigerTally_NOIDFA.framework, a su proyecto.

  • Versión IDFA

    Después de agregar el archivo, establezca la opción Embed de AliTigerTally_IDFA.framework en Do Not Embed.

  • Versión sin IDFA

    Agregue AliTigerTally_NOIDFA.framework a la sección Frameworks, Libraries, and Embedded Content de su proyecto y establezca Embed en Do Not Embed.

Agregue el archivo del SDK del módulo Captcha, AliCaptcha.framework, a su proyecto y establezca su opción Embed en Do Not Embed.

Agregue el archivo de recursos AliCaptcha.bundle a su proyecto desde Build Phases > Copy Bundle Resources.

Paso 3: Agregar bibliotecas de dependencias

En la sección Frameworks, Libraries, and Embedded Content de su proyecto de Xcode, agregue a continuación las bibliotecas de dependencias.

Biblioteca de dependencia

Obligatoria para IDFA

Obligatoria para la versión sin IDFA

libc++.tbd

Sí

Sí

libresolv.9.tbd

Sí

Sí

CoreTelephony.framework

Sí

Sí

AdSupport.framework

Sí

No

AppTrackingTransparency.framework

Sí

No

Paso 4: Agregar una marca del vinculador

En Build Settings, busque Other Linker Flags y agregue la marca -ObjC.

Paso 5: Agregar el código de integración

1. Agregar el archivo de encabezado

  • Para la versión IDFA, agregue la siguiente instrucción de importación:

    #import <AliTigerTally_IDFA/AliTigerTally.h>
  • Para la versión sin IDFA, agregue la siguiente instrucción de importación:

    #import <AliTigerTally_NOIDFA/AliTigerTally.h> 

2. Configurar la firma de datos

  1. Establezca un identificador personalizado de usuario final para su negocio. Esto le permite configurar las políticas de protección de WAF con mayor flexibilidad.

    /**
    * Sets the user account.
    *
    * @param account   The account information.
    */
    - (void)setAccount:(NSString *)account;
    • Parámetros:

      • account: Tipo: NSString. Una cadena que identifica a un usuario. Se recomienda utilizar un formato anonimizado.

    • Valor devuelto: Ninguna.

    • Ejemplo:

      // For guest users, you can skip setAccount and initialize directly. After a user logs in, call setAccount and re-initialize.
      [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
  2. Inicialice el SDK para recopilar datos.

    Cada inicialización recopila la información del dispositivo una sola vez. Llame de nuevo a la función init para reinicializar la recopilación de datos en distintos escenarios de negocio.

    La inicialización admite tres modos de recopilación de datos: completo, de privacidad personalizada y sin privacidad. El modo sin privacidad excluye los campos relacionados con la privacidad del usuario final, como IDFA e IDFV.

    Nota

    Para cumplir con sus requisitos internos, seleccione un modo de recopilación de datos adecuado que garantice la integridad de la información. Entre más completos sean los datos, mejor será la detección de amenazas.

    // Initialization callback. Returns the status code of the interface call.
    typedef void (^TTInitListener)(int);
    
    /**
     * Initializes the SDK.
     *
     * @param appkey        The key.
     * @param options       Optional parameters.
     * @param onInitFinish  The callback for when initialization is complete.
     * @return Indicates whether the initialization was successful.
     */
    - (int)init:(NSString *)appkey collectType:(TTCollectType)type options:(NSMutableDictionary *_Nullable)options listener:(TTInitListener _Nullable)onInitFinish;
    • Parámetros:

      • appkey: Tipo: NSString. Su clave de autenticación del SDK.

      • collectType: Tipo: TTCollectType. El modo de recopilación de datos. Valores válidos:

        Campo

        Descripción

        Ejemplo

        TT_DEFAULT

        Recopila todos los datos.

        TT_DEFAULT

        TT_NO_BASIC_DATA

        No recopila datos básicos del dispositivo.

        Esto incluye el nombre del dispositivo, la versión del sistema operativo y la resolución de pantalla.

        TT_NO_X | TT_NO_Y

        (donde X e Y representan los tipos de campos de datos que se excluirán)

        TT_NO_UNIQUE_DATA

        No recopila datos de identificadores únicos.

        Esto incluye IDFV e IDFA.

        TT_NO_EXTRA_DATA

        No recopila datos extendidos del dispositivo.

        Esto incluye la información de Wi-Fi conectada (SSID, BSSID) y la lista de redes Wi-Fi cercanas.

        TT_NOT_GRANTED

        No recopila ninguno de los datos anteriores relacionados con la privacidad.

        TT_NOT_GRANTED

      • options: Tipo: NSMutableDictionary. Parámetros opcionales para la recopilación de datos. El valor predeterminado es nil. Están disponibles los siguientes parámetros:

        Campo

        Descripción

        Ejemplo

        IPv6

        Especifica si se utiliza un nombre de dominio IPv6 para reportar la información del dispositivo.

        • 0 (predeterminado): Utiliza un nombre de dominio IPv4.

        • 1: Utiliza un nombre de dominio IPv6.

        1

        Intl

        Especifica si se utiliza un nombre de dominio fuera de China continental para reportar la información del dispositivo.

        • 0 (predeterminado): Reporta a China continental.

        • 1: Reporta a regiones fuera de China continental.

        1

        CustomUrl

        Establece el nombre de dominio del servidor de reporte de datos.

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

        CustomHost

        Establece el host del servidor de reporte de datos.

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

        Nota

        Para la mayoría de los sitios internacionales, configure únicamente el parámetro Intl. Si necesita reportar datos a un sitio específico, configure los parámetros CustomUrl y CustomHost. Los sitios disponibles incluyen:

        • Si Intl se establece en 0, los datos se reportan al sitio predeterminado en China (Shanghai): https://cloudauth-device.cn-shanghai.aliyuncs.com

        • Si Intl se establece en 1:

          • El sitio predeterminado es Singapore: https://cloudauth-device.ap-southeast-1.aliyuncs.com

          • Indonesia (Jakarta): https://cloudauth-device.ap-southeast-5.aliyuncs.com

          • US (Silicon Valley): https://cloudauth-device.us-west-1.aliyuncs.com

          • Germany (Frankfurt): https://cloudauth-device.eu-central-1.aliyuncs.com

          • China (Hong Kong): https://cloudauth-device.cn-hongkong.aliyuncs.com

      • listener: Tipo: TTInitListener. La interfaz de devolución de llamada de inicialización del SDK. Esta devolución de llamada sirve para determinar el estado de la inicialización. El valor predeterminado es nil.

        TTCode

        Código

        Descripción

        TT_SUCCESS

        0

        Inicialización exitosa.

        TT_NOT_INIT

        -1

        El SDK no está inicializado.

        TT_NOT_PERMISSION

        -2

        No se otorgaron los permisos obligatorios de iOS.

        TT_UNKNOWN_ERROR

        -3

        Ocurrió un error desconocido del sistema.

        TT_NETWORK_ERROR

        -4

        Ocurrió un error de red.

        TT_NETWORK_ERROR_EMPTY

        -5

        Ocurrió un error de red. El contenido devuelto es una cadena vacía.

        TT_NETWORK_ERROR_INVALID

        -6

        El formato de la respuesta de red no es válido.

        TT_PARSE_SRV_CFG_ERROR

        -7

        Error al analizar sintácticamente la configuración del servidor.

        TT_NETWORK_RET_CODE_ERROR

        -8

        La puerta de enlace devolvió una respuesta con errores.

        TT_APPKEY_EMPTY

        -9

        El appkey está vacío.

        TT_PARAMS_ERROR

        -10

        Ocurrieron otros errores de parámetros.

        TT_FGKEY_ERROR

        -11

        Ocurrió un error de cálculo de clave.

        TT_APPKEY_ERROR

        -12

        La versión del SDK no coincide con la versión del appkey.

    • Valor devuelto: Tipo: int. Un código de error. 0 indica éxito y un número negativo indica un fallo.

    • Ejemplo:

      // The appkey is the authentication key assigned to you on the Alibaba Cloud console.
      NSString *appKey = @"xxxxxxxxxxxxxxxxxxxxx";
      // Optional parameters. You can configure IPv6 and reporting from outside the Chinese mainland.
      NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
      [options setValue:@"0" forKey:@"IPv6"];     // Use IPv4.
      [options setValue:@"0" forKey:@"Intl"];     // Report data from within the Chinese mainland.
      // [options setValue:@"1" forKey:@"Intl"];  // Report data from outside the Chinese mainland.
      // Report data to the US (West) region.
      // [options setValue:@"https://cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomUrl"];
      // [options setValue:@"cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomHost"];
      
      // An initialization call collects device information once. You can call the init function again to collect information for different business scenarios.
      // Full data collection.
      if (0 == [[AliTigerTally sharedInstance] init:appkey collectType:TT_DEFAULT options:options listener:nil]) {
          NSLog(@"Initialization successful");
      } else {
          NSLog(@"Initialization failed");
      }
      
      // To collect specific types of private data, use the "|" operator to specify multiple types.
      TTCollectType collectPrivacy = TT_NO_BASIC_DATA | TT_NO_EXTRA_DATA;
      int ret = [[AliTigerTally sharedInstance] init:appkey collectType:collectPrivacy options:options listener:nil];
      
      // Do not collect private data.
      int ret = [[AliTigerTally sharedInstance] init:appkey collectType:TT_NOT_GRANTED options:options listener:nil];
  3. Generación de hash de datos.

    Esta interfaz de firma personalizada genera un hash de los datos de entrada para producir una cadena whash, la cual funciona como una firma personalizada. Para las solicitudes POST, PUT y PATCH, utilice el cuerpo de la solicitud como entrada. Para las solicitudes GET y DELETE, utilice la URL completa. Además, debe agregar la cadena whash generada al campo ali_sign_whash en el encabezado de la solicitud HTTP.

    // Request type:
    typedef NS_ENUM(NSInteger, TTRequestType) {
        TT_GET=0, TT_POST, TT_PUT, TT_PATCH, TT_DELETE
    };
    
    /**
     * Hashes the custom signature data.
     * @param type  The data type.
     * @param input The data to be signed.
     * @return The whash string.
     */
    - (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;
    • Parámetros:

      • type: Tipo: TTTypeRequest. El tipo de datos. Valores válidos:

        • GET: Datos de solicitud GET.

        • POST: Datos de solicitud POST.

        • PUT: Datos de solicitud PUT.

        • PATCH: Datos de solicitud PATCH.

        • DELETE: Datos de solicitud DELETE.

      • input: Tipo: NSData. Los datos que se firmarán. Pase el cuerpo o la URL según el tipo.

    • Valor devuelto: Tipo: NSString. La cadena whash.

    • Ejemplo:

      // GET request
      NSString *url = @"https://tigertally.aliyun.com/apptest";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@", whash);
      
      // POST request
      NSString *body = @"hello world";
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@", whash);
    Nota

    Solo es necesario llamar a esta interfaz si habilitó la opción de firma personalizada en la consola de WAF. Si utiliza la configuración de firma predeterminada, omita este paso.

  4. Firma de datos.

    Este método utiliza tecnología VMP para firmar los datos de entrada y devuelve una cadena wtoken para la autenticación de la solicitud.

    /**
     * Signs the data.
     * @param input The data to be signed.
     * @return The wtoken string.
     */
    - (NSString *)vmpSign:(NSData *)input;
    • Parámetros:

      • input: Tipo: NSData. Los datos que se firmarán. Por lo general, se trata del cuerpo de la solicitud completo o del valor whash obtenido con la firma personalizada.

    • Valor devuelto: Tipo: NSString. La cadena wtoken.

    • Ejemplo:

      // Default signature configured in the console (custom signing is not selected).
      NSString *body = @"hello world";
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"wtoken: %@", wtoken);
      
      // Custom signing configured in the console.
      // Custom signing for a POST request.
      NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]];
      NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
      NSLog(@"whash: %@, wtoken: %@", whash, wtoken);
      
      // Custom signing for a GET request.
      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);
      Nota
      • Cuando utilice la firma personalizada, la entrada de la interfaz vmpSign debe ser la cadena whash generada por vmpHash. Al configurar una política basada en escenarios para App Protection, debe establecer el campo de firma personalizada en ali_sign_whash.

      • Cuando llame a vmpHash para generar un valor whash en una solicitud GET, asegúrese de que la URL de entrada sea idéntica a la URL final de la solicitud de red. Preste especial atención a la codificación de la URL, ya que algunos frameworks codifican automáticamente ciertos caracteres o parámetros.

      • La entrada de la interfaz vmpHash no puede ser una matriz de bytes ni una cadena vacía. Si la entrada es una URL, debe incluir una ruta o un parámetro.

      • Al llamar a vmpSign, si el cuerpo de la solicitud está vacío (por ejemplo, en una solicitud GET o en una solicitud POST sin cuerpo), pase nil o la representación NSData de una cadena vacía, como [@"" dataUsingEncoding:NSUTF8StringEncoding].

      • Si whash o wtoken devuelve alguna de las siguientes cadenas, significa que ocurrió un error:

        • you must call init first: No se llamó a la función init.

        • you must input correct data: Los datos de entrada no son válidos.

        • you must input correct type: El tipo de entrada no es válido.

3. Verificación secundaria

  1. Compruebe el resultado.

    Para determinar si se requiere una verificación secundaria, compruebe la cookie y el cuerpo de la respuesta. Si el encabezado contiene varios campos Set-Cookie, combínelos en una sola cadena de cookies antes de llamar a esta interfaz.

    /**
     * Checks whether to perform secondary verification.
     *
     * @param cookie  The response cookie.
     * @param body    The response body.
     * @return 0: Pass, 1: Secondary verification required.
     */
    - (int)cptCheck:(NSString *)cookie body:(NSData *)body;
    • Parámetros:

      • cookie: Tipo: NSString. Todas las cookies de la respuesta de la solicitud.

      • body: Tipo: NSData. El cuerpo completo de la respuesta de la solicitud.

    • Valor devuelto: Tipo: int. El resultado de la decisión. 0 indica que la solicitud pasó la comprobación y 1 indica que se requiere una verificación secundaria.

      • Ejemplo:

      NSString *cookie = @"key1=value1;key2=value2;";
      NSData *body = xxx;
      int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
      NSLog(@"recheck: %d", recheck);
  2. Cree un control deslizante.

    Si el resultado de cptCheck indica que se necesita verificación, cree un objeto de control deslizante. El objeto TTCaptcha proporciona los métodos show y dismiss para controlar la ventana del control deslizante. Utilice TTOption para configurar sus parámetros y TTDelegate para gestionar las devoluciones de llamada de su estado. Para personalizar la ventana del control deslizante, debe proporcionar una URL hacia una página personalizada, la cual puede ser un archivo HTML local o una página remota.

    /**
     * Displays the slider for verification.
     *
     * @param view      The parent component.
     * @param option    The parameters.
     * @param detegate  The callback protocol.
     */
    - (TTCaptcha *)cptCreate:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)detegate;
    
    @protocol TTDelegate <NSObject>
    @required
    // Slider verification is successful.
    - (void)success:(TTCaptcha *)captcha data:(NSString *)data;
    
    // Slider verification failed.
    - (void)failed:(TTCaptcha *)captcha code:(NSString *)code;
    @end
    
    @interface TTOption : NSObject
    // Tap to cancel.
    @property (nonatomic, assign) BOOL cancelable;
    
    // Custom page.
    @property (nonatomic, strong) NSString *customUri;
    
    // Language.
    @property (nonatomic, strong) NSString *language;
    @end
    
    @interface TTCaptcha : NSObject
    
    - (instancetype)init:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)delegate;
    
    // Obtains the slider traceId for data analytics.
    - (NSString *)getTraceId;
    
    // Displays the slider.
    - (void)show;
    
    // Dismisses the slider.
    - (void)dismiss;
    
    @end
    • Parámetros:

      • view: Tipo: View. La vista de la página actual.

      • option: Tipo: TTOption. Los parámetros de configuración del control deslizante.

      • delegate: Tipo: TTDelegate. La devolución de llamada del estado del control deslizante.

    • Valor devuelto: Tipo: TTCaptcha. El objeto del control deslizante.

    • Ejemplo:

      #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];
      Nota

      Un error de "Verification failed" indica que se detectó una excepción durante o después de la interacción del usuario con el control deslizante.

      En la siguiente tabla se describen los códigos de error.

      • 1001: Error en el desafío de verificación.

      • 1002: Ocurrió una excepción del sistema.

      • 1003: Se especificó un parámetro no válido.

      • 1005: Se canceló la verificación.

      • 8001: Error al mostrar el control deslizante.

      • 8002: Ocurrió una excepción en los datos de verificación del control deslizante.

      • 8003: Ocurrió una excepción interna durante la verificación del control deslizante.

      • 8004: Ocurrió un error de red.

Práctica recomendada

#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];
    // Do any additional setup after loading the view.

    [self doTest];
}

- (void)doTest {

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        // Initialize the SDK.
        NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
        // [options setValue:@"1" forKey:@"Intl"];     // Configure for international reporting.
        int code = [[AliTigerTally sharedInstance] init:kAppkey collectType:TT_DEFAULT options:options listener:nil];
        NSLog(@"tigertally init: %d", code);

        // Do not make a synchronous call immediately.
        [NSThread sleepForTimeInterval:2.0];

        // Generate the signature.
        NSString *body = @"hello world";
        NSString *whash = nil, *wtoken = nil;
        // Custom signing.
        whash  = [[AliTigerTally sharedInstance] vmpHash:TT_Post input:[body dataUsingEncoding:NSUTF8StringEncoding]];
        wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
        NSLog(@"tigertally vmp: %@, %@", whash, wtoken);

        // Default signing.
        // 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