全部產品
Search
文件中心

Web Application Firewall:iOS應用整合SDK

更新時間:Dec 10, 2025

您需要在應用中整合SDK,才能在控制台BOT管理中配置App防爬情境化規則。本文介紹了如何為iOS應用整合WAF App防護SDK(以下簡稱SDK)。

背景資訊

App防護SDK主要用於對通過App用戶端發起的請求進行簽名。WAF服務端通過校正App請求籤名,識別App業務中的風險、攔截惡意請求,實現App防護的目的。

使用限制

  • iOS SDK分為IDFA(Identifier for Advertising,簡稱IDFA) 版本和非IDFA版本,對應的SDK檔案分別是:

    • AliTigerTally_IDFA.framework

    • AliTigerTally_NOIDFA.framework

    如果您的iOS專案中使用了IDFA,推薦您整合AliTigerTally_IDFA版本SDK,否則請使用AliTigerTally_NOIDFA版本SDK。

  • init初始化介面存在耗時操作,為確保安全能力完整性,建議在調用init介面後,確保至少間隔2秒再調用後續的vmpSign簽名介面。此間隔為推薦值(非強制要求),旨在提升SDK的防護效果。實際調用中可根據業務需求靈活調整,但縮短間隔可能影響安全能力的完整生效。

  • iOS應用對應的iOS版本是9.0及以上,否則不支援整合App防護SDK。

前提條件

  • 已擷取iOS應用對應的SDK。

    擷取方法:請提交工單,聯絡產品技術專家擷取SDK。

    說明

    iOS應用對應的SDK檔案名稱為tigertally-X.Y.Z-xxxx-ios.zip,X.Y.Z表示版本號碼,其中包含2個framework檔案,2個xcframework檔案。

  • 已擷取SDK認證密鑰(即appkey)。

    開啟BOT管理後,即可在BOT管理 > App防护列表中,單擊获取并复制appkey,擷取SDK認證密鑰。該密鑰用於發起SDK初始化請求,需要在整合代碼中使用。

    image

    說明

    每個阿里雲帳號擁有唯一的appkey(適用於所有接入WAF防護的網域名稱),且Android、iOS和Harmony應用整合SDK時都使用該appkey。

    認證密鑰樣本:****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****。

步驟一:建立工程

以Xcode環境為例,建立一個iOS工程,並按照設定精靈完成建立。建立好的工程目錄如下圖所示。image.png

步驟二:整合framework包

將擷取到的SDK檔案AliTigerTally_IDFA.frameworkAliTigerTally_NOIDFA.framework添加到相應專案。

  • IDFA版本image.png

  • 無IDFA版本image.png

將擷取到的驗證碼模組SDK檔案AliCaptcha.framework添加到相應專案。

image.png

將資源檔AliCaptcha.bundle添加到相應專案。

image.png

步驟三:添加依賴庫

依賴庫

IDFA版本是否需要

無IDFA版本是否需要

libc++.tbd

libresolv.9.tbd

CoreTelephony.framework

AdSupport.framework

AppTrackingTransparency.framework

image.png

步驟四:編輯選項

Other Linker Flags選項中添加-ObjCimage.png

步驟五:添加整合代碼

1. 添加標頭檔

  • IDFA版本配置資訊如下:

    #import <AliTigerTally_IDFA/AliTigerTally.h>
  • 非IDFA版本配置資訊如下:

    #import <AliTigerTally_NOIDFA/AliTigerTally.h> 

2. 設定資料簽名

  1. 設定您業務中自訂的終端使用者標識,方便您更靈活地配置WAF防護策略。

    /**
    * 設定使用者賬戶
    *
    * @param account   賬戶資訊
    */
    - (void)setAccount:(NSString *)account;
    • 參數說明:

      • account:NSString類型,表示標識一個使用者的字串,建議您使用脫敏後的格式。

    • 傳回值:無返回,或返回void。

    • 範例程式碼:

      // 遊客身份可以暫時先不setAccount, 直接初始化; 登入以後調用setAccount和重新初始化
      [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
  2. 初始化SDK,執行一次初始化採集。

    一次初始化採集表示採集一次終端裝置資訊,您可以根據業務的不同,重新調用init函數進行初始化採集。

    初始化採集分為三種模式:全量採集、自訂隱私採集、非隱私採集(不採集涉及終端裝置使用者隱私的欄位,包括: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

        表示不採集基礎裝置資料。

        包括:裝置名稱、系統版本號碼、螢幕解析度。

        TT_NO_X | TT_NO_Y

        (表示既不採集X又不採集Y, X、Y表示具體採集項的欄位類型名)

        TT_NO_UNIQUE_DATA

        表示不採集唯一標識資料。

        包括:IDFV、IDFA。

        TT_NO_EXTRA_DATA

        表示不採集擴充裝置資料。

        包括:串連的WIFI資訊(SSID、BSSID)、附近WIFI列表。

        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

        設定資料上報伺服器host

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

        說明

        常見國際網站設定Intl參數即可,只有指定網站上報需要設定CustomUrl和CustomHost,網站列表如下:

        • Intl = 0時,預設為上海網站: https://cloudauth-device.cn-shanghai.aliyuncs.com

        • Intl = 1時:

          • 預設為新加坡網站: https://cloudauth-device.ap-southeast-1.aliyuncs.com

          • 印尼(雅加達)網站: https://cloudauth-device.ap-southeast-5.aliyuncs.com

          • 美國(矽谷)網站: 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

        Code

        備忘

        TT_SUCCESS

        0

        SDK初始化成功

        TT_NOT_INIT

        -1

        SDK未調用初始化

        TT_NOT_PERMISSION

        -2

        SDK需要的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

        AppKey為空白

        TT_PARAMS_ERROR

        -10

        其他參數錯誤

        TT_FGKEY_ERROR

        -11

        密鑰計算錯誤

        TT_APPKEY_ERROR

        -12

        SDK版本和AppKey版本不匹配

    • 傳回值:int類型,返回錯誤碼。0表示成功;負數表示失敗。

    • 範例程式碼:

      // appkey代表阿里雲客戶平台分配的認證密鑰
      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初始化採集
      // 全量採集
      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. 資料雜湊。

    自訂加簽介面,對傳入的資料input進行計算,產生一個whash字串作為自訂簽名資料。Post、Put、Patch請求需要傳入request body,Get、Delete請求傳入完整的URL地址。同時,whash字串需要添加到HTTP請求header的ali_sign_whash中。

    // 請求類型:
    typedef NS_ENUM(NSInteger, TTRequestType) {
        TT_GET=0, TT_POST, TT_PUT, TT_PATCH, TT_DELETE
    };
    
    /**
     * 自訂簽名資料 hash
     * @param type  資料類型
     * @param input 簽名資料
     * @return whash
     */
    - (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;
    • 參數說明:

      • type:TTTypeRequest類型,設定資料類型。取值:

        • GET:表示Get請求資料。

        • POST:表示Post請求資料。

        • PUT:表示Put請求資料。

        • PATCH:表示Patch請求資料。

        • DELETE:表示Delete請求資料。

      • input:NSData類型,表示待加簽的資料,根據type傳入body或者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);
    說明

    控制台配置預設簽名(即不勾選自訂加簽)不需要調用該介面,勾選自訂加簽時需要在資料簽名前調用該介面進行雜湊校正。

  4. 資料簽名。

    使用vmp技術對input的資料進行簽名處理,並且返回wtoken字串用於請求認證。

    /**
     * 資料簽名
     * @param input 簽名資料
     * @return wtoken
     */
    - (NSString *)vmpSign:(NSData *)input;
    • 參數說明:

      • input:NSData類型,表示待簽名的資料,一般是整個請求體的request body,或者是自訂加簽的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);
      說明
      • 調用vmpHash進行自訂加簽時,簽名介面vmpSign的參數input為產生的whash字串,且在配置App防爬情境化策略時,自訂加簽欄位的值需設定為ali_sign_whash。

      • 調用vmpHash產生Get請求的whash時,必須保證輸入的URL地址和最終網路請求的URL一致,特別需要注意UrlEncode情況,部分架構會自動對中文或者參數進行UrlEncode編碼。

      • 介面vmpHash的參數input不支援位元組或者Null 字元串,輸入為URL時必須存在Path或者Param。

      • 調用vmpSign時,如果請求體為空白(例如,Post請求或Get請求的body為空白),則填寫Null 物件nil或Null 字元串的NSData值(例如[@"" dataUsingEncoding:NSUTF8StringEncoding])。

      • 當whash或wtoken為以下字串時表示初始化流程存在異常:

        • you must call init first:表示未調用init函數。

        • you must input correct data:表示傳入資料錯誤。

        • you must input correct type:表示傳入類型錯誤。

3. 二次校正

  1. 判斷結果。

    根據responsecookiebody欄位判斷是否要進行二次校正。header中可能存在多個Set-Cookie,需要按照cookie格式合并後調用該介面。

    /**
     * 是否進行二次校正
     *
     * @param cookie  response cokie
     * @param body    response body
     * @return 0:通過 1:二次校正
     */
    - (int)cptCheck:(NSString *)cookie body:(NSData *)body;
    • 參數說明:

      • cookie:NSString類型,佈建要求response中全部cookie

      • body:NSData類型,佈建要求response中全部body

    • 傳回值: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封裝了滑塊可配置的參數,TTListener包含了滑塊的2種回調狀態。如果需要自訂滑塊視窗頁面需要傳入自訂頁面地址,支援本地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;
    
    // 擷取滑塊traceId, 用於資料統計
    - (NSString *)getTraceId;
    
    // 滑塊調用顯示
    - (void)show;
    
    // 滑塊取消
    - (void)dismiss;
    
    @end
    • 參數說明:

      • view:View類型,設定當前頁面view。

      • option:TTOption類型,設定滑塊配置參數。

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


    [self doTest];
}

- (void)doTest {

    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        // 初始化
        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:@"確定" 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:@"確定" style:UIAlertActionStyleDefault handler:nil]];
    [self presentViewController:alert animated:true completion:nil];
}

@end