Todos os produtos
Search
Central de documentação

Web Application Firewall:Integração do SDK para aplicativos iOS

Última atualização: Jul 03, 2026

Para configurar políticas de mitigação baseadas em cenários para seu aplicativo no console Bot Management, integre o App Protection SDK do Web Application Firewall (WAF). Este tópico descreve como integrar o App Protection SDK a um aplicativo iOS.

Informações básicas

O App Protection SDK assina as requisições originadas pelo cliente do aplicativo. O servidor do WAF verifica a assinatura da requisição para identificar e bloquear solicitações maliciosas, protegendo seu aplicativo.

Limitações

  • O SDK para iOS está disponível em duas versões: uma com Identifier for Advertising (IDFA) e outra sem IDFA. Os arquivos correspondentes do SDK são:

    • AliTigerTally_IDFA.framework

    • AliTigerTally_NOIDFA.framework

    Se seu projeto iOS utilizar IDFA, recomendamos a integração da versão AliTigerTally_IDFA do SDK. Caso contrário, utilize a versão AliTigerTally_NOIDFA.

  • A interface init pode demandar tempo de processamento. Para garantir proteção ideal, aguarde pelo menos 2 segundos após chamar a interface init antes de invocar a interface vmpSign. Embora esse atraso não seja obrigatório, um intervalo menor pode impedir que os recursos de segurança funcionem plenamente. Ajuste o intervalo conforme as necessidades do seu negócio.

  • O App Protection SDK exige a versão 9,0 ou superior do iOS.

Pré-requisitos

  • Obtenha o SDK para seu aplicativo iOS.

    Para obter o SDK, envie um ticket para entrar em contato com um especialista técnico do produto.

    Nota

    O arquivo do SDK para um aplicativo iOS chama-se tigertally-X.Y.Z-xxxx-ios.zip, onde X.Y.Z representa o número da versão. O arquivo contém dois arquivos framework e dois arquivos xcframework.

  • Obtenha a chave de autenticação do SDK (também conhecida como appkey).

    Após ativar o Bot Management, acesse a página Bot Management > App Protection. Na lista de aplicativos, clique em Obtain and Copy AppKey para obter a chave de autenticação do SDK. Essa chave é necessária para a inicialização do SDK e deve ser incluída no seu código de integração.

    image

    Nota

    Cada conta Alibaba Cloud possui uma appkey exclusiva para todos os nomes de domínio protegidos pelo WAF. Essa appkey é usada na integração do SDK em aplicativos Android, iOS e HarmonyOS.

    Exemplo de chave de autenticação: ****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.

Etapa 1: Crie um projeto

No Xcode, crie um projeto iOS usando o assistente de configuração. A figura a seguir mostra um exemplo de diretório de projeto.image.png

Etapa 2: Integre o framework

Adicione o arquivo do SDK, AliTigerTally_IDFA.framework ou AliTigerTally_NOIDFA.framework, ao seu projeto.

  • Versão com IDFA image.png

  • Versão sem IDFA image.png

Adicione o arquivo do módulo Captcha, AliCaptcha.framework, ao seu projeto.

image.png

Adicione o arquivo de recurso AliCaptcha.bundle ao seu projeto.

image.png

Etapa 3: Adicione bibliotecas de dependência

Biblioteca de dependência

Necessária para IDFA

Necessária para versão sem IDFA

libc++.tbd

Sim

Sim

libresolv.9.tbd

Sim

Sim

CoreTelephony.framework

Sim

Sim

AdSupport.framework

Sim

Não

AppTrackingTransparency.framework

Sim

Não

image.png

Etapa 4: Adicione uma flag de linker

Na configuração Other Linker Flags, adicione a flag -ObjC.image.png

Etapa 5: Adicione o código de integração

1. Adicione o arquivo de cabeçalho

  • Para a versão com IDFA, adicione a seguinte instrução de importação:

    #import <AliTigerTally_IDFA/AliTigerTally.h>
  • Para a versão sem IDFA, adicione a seguinte instrução de importação:

    #import <AliTigerTally_NOIDFA/AliTigerTally.h> 

2. Configure a assinatura de dados

  1. Defina um identificador personalizado de usuário final para o seu negócio. Isso permite configurar políticas de proteção do WAF com maior flexibilidade.

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

      • account: Tipo: NSString. Uma string que identifica um usuário. Recomendamos o uso de um formato anonimizado.

    • Valor de retorno: Nenhum.

    • Exemplo:

      // 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. Inicialize o SDK para coletar dados.

    Cada inicialização coleta informações do dispositivo uma única vez. Chame a função init novamente para reiniciar a coleta de dados em diferentes cenários de negócio.

    A inicialização oferece suporte a três modos de coleta de dados: completo, privacidade personalizada e sem privacidade. O modo sem privacidade exclui campos relacionados à privacidade do usuário final, como IDFA e IDFV.

    Nota

    Para atender aos seus requisitos internos, selecione um modo de coleta de dados adequado que garanta a integridade das informações. Dados mais completos melhoram a detecção de ameaças.

    // 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. Sua chave de autenticação do SDK.

      • collectType: Tipo: TTCollectType. O modo de coleta de dados. Valores válidos:

        Campo

        Descrição

        Exemplo

        TT_DEFAULT

        Coleta todos os dados.

        TT_DEFAULT

        TT_NO_BASIC_DATA

        Não coleta dados básicos do dispositivo.

        Inclui nome do dispositivo, versão do sistema operacional e resolução de tela.

        TT_NO_X | TT_NO_Y

        (onde X e Y representam os tipos de campos de dados a excluir)

        TT_NO_UNIQUE_DATA

        Não coleta dados de identificadores exclusivos.

        Inclui IDFV e IDFA.

        TT_NO_EXTRA_DATA

        Não coleta dados estendidos do dispositivo.

        Inclui informações de Wi-Fi conectado (SSID, BSSID) e a lista de redes Wi-Fi próximas.

        TT_NOT_GRANTED

        Não coleta nenhum dos dados relacionados à privacidade mencionados anteriormente.

        TT_NOT_GRANTED

      • options: Tipo: NSMutableDictionary. Parâmetros opcionais para coleta de dados. O valor padrão é nil. Estão disponíveis os seguintes parâmetros:

        Campo

        Descrição

        Exemplo

        IPv6

        Especifica se deve usar um nome de domínio IPv6 para relatar informações do dispositivo.

        • 0 (padrão): Usa um nome de domínio IPv4.

        • 1: Usa um nome de domínio IPv6.

        1

        Intl

        Especifica se deve usar um nome de domínio fora da China continental para relatar informações do dispositivo.

        • 0 (padrão): Relata para a China continental.

        • 1: Relata para regiões fora da China continental.

        1

        CustomUrl

        Define o nome de domínio do servidor de relatório de dados.

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

        CustomHost

        Define o host do servidor de relatório de dados.

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

        Nota

        Para a maioria dos sites internacionais, defina apenas o parâmetro Intl. Para relatar dados a um site específico, configure os parâmetros CustomUrl e CustomHost. Os sites disponíveis incluem:

        • Se Intl estiver definido como 0, os dados serão relatados ao site padrão na China (Xangai): https://cloudauth-device.cn-shanghai.aliyuncs.com

        • Se Intl estiver definido como 1:

          • O site padrão é Singapura: https://cloudauth-device.ap-southeast-1.aliyuncs.com

          • Indonésia (Jacarta): https://cloudauth-device.ap-southeast-5.aliyuncs.com

          • EUA (Vale do Silício): https://cloudauth-device.us-west-1.aliyuncs.com

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

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

      • listener: Tipo: TTInitListener. A interface de callback de inicialização do SDK. Use este callback para determinar o status da inicialização. O valor padrão é nil.

        TTCode

        Código

        Descrição

        TT_SUCCESS

        0

        Inicialização bem-sucedida.

        TT_NOT_INIT

        -1

        O SDK não foi inicializado.

        TT_NOT_PERMISSION

        -2

        As permissões necessárias do iOS não foram concedidas.

        TT_UNKNOWN_ERROR

        -3

        Ocorreu um erro desconhecido do sistema.

        TT_NETWORK_ERROR

        -4

        Ocorreu um erro de rede.

        TT_NETWORK_ERROR_EMPTY

        -5

        Ocorreu um erro de rede. O conteúdo retornado é uma string vazia.

        TT_NETWORK_ERROR_INVALID

        -6

        O formato da resposta de rede é inválido.

        TT_PARSE_SRV_CFG_ERROR

        -7

        Falha ao analisar a configuração do servidor.

        TT_NETWORK_RET_CODE_ERROR

        -8

        O gateway retornou uma resposta de falha.

        TT_APPKEY_EMPTY

        -9

        A appkey está vazia.

        TT_PARAMS_ERROR

        -10

        Ocorreram outros erros de parâmetro.

        TT_FGKEY_ERROR

        -11

        Ocorreu um erro de cálculo de chave.

        TT_APPKEY_ERROR

        -12

        A versão do SDK não corresponde à versão da appkey.

    • Valor de retorno: Tipo: int. Um código de erro. 0 indica sucesso, e um número negativo indica falha.

    • Exemplo:

      // 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. Hashing de dados.

    Esta interface de assinatura personalizada aplica hash aos dados de entrada para gerar uma string whash, que serve como uma assinatura personalizada. Para requisições POST, PUT e PATCH, use o corpo da requisição como entrada. Para requisições GET e DELETE, use a URL completa. Adicione também a string whash gerada ao campo ali_sign_whash no cabeçalho da requisição 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. O tipo de dados. Valores válidos:

        • GET: Dados de requisição GET.

        • POST: Dados de requisição POST.

        • PUT: Dados de requisição PUT.

        • PATCH: Dados de requisição PATCH.

        • DELETE: Dados de requisição DELETE.

      • input: Tipo: NSData. Os dados a serem assinados. Passe o corpo ou a URL com base no tipo.

    • Valor de retorno: Tipo: NSString. A string whash.

    • Exemplo:

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

    Chame esta interface apenas se você tiver ativado a opção de assinatura personalizada no console do WAF. Caso utilize a configuração padrão de assinatura, pule esta etapa.

  4. Assinatura de dados.

    Este método utiliza a tecnologia VMP para assinar os dados de entrada e retorna uma string wtoken para autenticação da requisição.

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

      • input: Tipo: NSData. Os dados a serem assinados. Geralmente, trata-se de todo o corpo da requisição ou do whash proveniente da assinatura personalizada.

    • Valor de retorno: Tipo: NSString. A string wtoken.

    • Exemplo:

      // 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
      • Ao usar assinatura personalizada, a entrada para a interface vmpSign deve ser a string whash gerada por vmpHash. Ao configurar uma política baseada em cenário para App Protection, defina o Custom Signing Field como ali_sign_whash.

      • Ao chamar vmpHash para gerar um whash para uma requisição GET, garanta que a URL de entrada seja idêntica à URL final da requisição de rede. Preste atenção especial à codificação da URL, pois alguns frameworks codificam caracteres ou parâmetros automaticamente.

      • A entrada para a interface vmpHash não pode ser um array de bytes ou uma string vazia. Se a entrada for uma URL, ela deve incluir um caminho ou um parâmetro.

      • Ao chamar vmpSign, se o corpo da requisição estiver vazio (por exemplo, em uma requisição GET ou POST sem corpo), passe nil ou a representação NSData de uma string vazia, como [@"" dataUsingEncoding:NSUTF8StringEncoding].

      • Se whash ou wtoken retornar uma das strings a seguir, isso indica um erro:

        • you must call init first: A função init não foi chamada.

        • you must input correct data: Os dados de entrada são inválidos.

        • you must input correct type: O tipo de entrada é inválido.

3. Verificação secundária

  1. Verifique o resultado.

    Para determinar se a verificação secundária é necessária, verifique o cookie e o corpo da resposta. Se o cabeçalho contiver vários campos Set-Cookie, combine-os em uma única string de cookie antes de chamar esta interface.

    /**
     * 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. Todos os cookies na resposta da requisição.

      • body: Tipo: NSData. Todo o corpo da resposta da requisição.

    • Valor de retorno: Tipo: int. O resultado da decisão. 0 indica que a requisição foi aprovada, e 1 indica que a verificação secundária é necessária.

      • Exemplo:

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

    Se o resultado de cptCheck indicar necessidade de verificação, crie um objeto slider. O objeto TTCaptcha fornece os métodos show e dismiss para controlar a janela do slider. Use TTOption para configurar seus parâmetros e TTDelegate para lidar com os callbacks de status. Para personalizar a janela do slider, forneça uma URL para uma página personalizada, que pode ser um arquivo HTML local ou uma 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. A visualização da página atual.

      • option: Tipo: TTOption. Os parâmetros de configuração do slider.

      • delegate: Tipo: TTDelegate. O callback de status do slider.

    • Valor de retorno: Tipo: TTCaptcha. O objeto slider.

    • Exemplo:

      #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

      Um erro "Verification failed" indica que uma exceção foi detectada durante ou após a interação do usuário com o slider.

      A tabela a seguir descreve os códigos de erro.

      • 1001: Falha no desafio de verificação.

      • 1002: Ocorreu uma exceção do sistema.

      • 1003: Um parâmetro inválido foi especificado.

      • 1005: A verificação foi cancelada.

      • 8001: Falha ao exibir o slider.

      • 8002: Ocorreu uma exceção nos dados de verificação do slider.

      • 8003: Ocorreu uma exceção interna durante a verificação do slider.

      • 8004: Ocorreu um erro de rede.

Melhor prática

#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