iOS WKWebViewとUIWebView Cookieメカニズムの同期

18716 ワード

WKWebViewの概要
  • WKWebViewは、アップルがWWDC 2014で発売した次世代WebViewコンポーネントで、iOS 8や従来のUIWebViewよりも明らかな優位性を持っています.
  • より多くのHTML 5をサポートする機能
  • 最大60 fpsのスクロールリフレッシュ率および内蔵ジェスチャー
  • UIWebViewDelegateとUIWebViewを14クラスと3つのプロトコル
  • に分割
  • Safari同じJSエンジン
  • より少ないメモリ消費量

  • まず、WKWebViewの属性方法を簡単に理解してみましょう.
    // webview   
    @property (nonatomic, readonly, copy) WKWebViewConfiguration *configuration;
    
    //       
    WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
    
    //WKPreferences    
    config.preferences = [[WKPreferences alloc] init];
    //    0
    config.preferences.minimumFontSize = 10;
    //     YES
    config.preferences.javaScriptEnabled = YES;
    //  iOS    NO,            
    config.preferences.javaScriptCanOpenWindowsAutomatically = NO;
    
    
    //      
    @property (nullable, nonatomic, weak) id navigationDelegate;
    
    //       
    @property (nullable, nonatomic, weak) id  UIDelegate;
    
    //  UIWebView       API
    - (nullable WKNavigation *)loadRequest:(NSURLRequest *)request;
    
    //     HTML
    - (nullable WKNavigation *)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
     
    //     data
    - (nullable WKNavigation *)loadData:(NSData *)data MIMEType:(NSString*)MIMEType characterEncodingName:(NSString *)characterEncodingName baseURL:(NSURL *)baseURL NS_AVAILABLE(10_11, 9_0);
    
    //       
    - (void)stopLoading;
    
    
    //   JS  
    - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ __nullable)(__nullable id, NSError * __nullable error))completionHandler;
    
    
  • JSとWebViewのコンテンツインタラクション
  • //     ,     WKUserScript         
    @property (nonatomic, readonly, copy) NSArray *userScripts;
     
    //   JS
    - (void)addUserScript:(WKUserScript *)userScript;
     
    //        JS
    - (void)removeAllUserScripts;
     
    //   scriptMessageHandler    frames ,      
    // window.webkit.messageHandlers..postMessage()
    //     
    // JS           
    - (void)addScriptMessageHandler:(id)scriptMessageHandler name:(NSString *)name;
     
    //   name      scriptMessageHandler
    - (void)removeScriptMessageHandlerForName:(NSString *)name;
    
  • WKUserScript

  • WKuserContentControlでは、WKuserScriptに使用されます.WKUSerContentControlはJSと対話するためのクラスであり、注入されたJSはWKUSerScriptオブジェクトである.すべてのプロパティとメソッドは次のとおりです.
    // JS   
    @property (nonatomic, readonly, copy) NSString *source;
     
    // JS    
    @property (nonatomic, readonly) WKUserScriptInjectionTime injectionTime;
     
    //     ,  JS          frames     main frame.
    @property (nonatomic, readonly, getter=isForMainFrameOnly) BOOLforMainFrameOnly;
     
    //      ,    WKUserScript  
    // source:JS   
    // injectionTime:JS     
    // forMainFrameOnly:     main frame
    - (instancetype)initWithSource:(NSString *)source injectionTime:(WKUserScriptInjectionTime)injectionTime forMainFrameOnly:(BOOL)forMainFrameOnly;
     
    
  • WKNavigationDelegate
  • @protocol WKNavigationDelegate 
     
    @optional
     
    //        ,               。WebKit            ,     ,             
    //     。  ,  Safari      ,      。
    //        Request
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
     
    //         
    //          response
    //    response,  WKNavigationResponse    
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler;
     
    //  main frame        ,      
    - (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(null_unspecified WKNavigation *)navigation;
     
    //  main frame         ,      
    - (void)webView:(WKWebView *)webViewdidReceiveServerRedirectForProvisionalNavigation:(null_unspecified WKNavigation*)navigation;
     
    //  main frame         ,   
    - (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
     
    //  main frame web       ,   
    - (void)webView:(WKWebView *)webView didCommitNavigation:(null_unspecified WKNavigation *)navigation;
     
    //  main frame     ,   
    - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation;
     
    //  main frame         ,   
    - (void)webView:(WKWebView *)webView didFailNavigation:(null_unspecified WKNavigation *)navigation withError:(NSError *)error;
     
    //          API, AFN、UIWebView     API    
    - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *__nullable credential))completionHandler;
     
    //  web content     ,   
    - (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView NS_AVAILABLE(10_11, 9_0);
     
    @end
    
    
    typedef NS_ENUM(NSInteger, WKNavigationActionPolicy) {
        WKNavigationActionPolicyCancel,
        WKNavigationActionPolicyAllow,
    } NS_ENUM_AVAILABLE(10_10, 8_0);
    

  • WKWebViewとUIWebViewのCookie同期の問題
  • WKWebViewはiOS 9以降に発売されたwebviewコンポーネントであるため、それ以前はUIWebViewであった.したがって、iOS 8上のUIWebViewの使用に影響を及ぼさないためには、WKWebViewとUIWebViewに対してパッケージ化互換処理を行う必要があります.Cookieの同期問題は大きな穴の一つです.

  • WKWebViewのCookieメカニズム:
  • WKWebViewのCookieは、プライベートストレージWKWebsiteDataStoreに格納されます.WKWebsiteDataStoreには、cookies、disk、memory caches、WebSQL、IndexedDBデータベース、ローカルストレージなどのWebコンテンツが格納されています.
  • なぜWKWebsiteDataStoreがプライベートストレージコンテナだと言うのですかWKWebsiteDataStoreのヘッダファイルを開くと
  • が表示されます
    //defaultDataStore           
    + (WKWebsiteDataStore *)defaultDataStore;
    
    //nonPersistentDataStore             ,       
    + (WKWebsiteDataStore *)nonPersistentDataStore;
    
    //                    
    + (NSSet *)allWebsiteDataTypes;
    
    //                         
    
    - (void)fetchDataRecordsOfTypes:(NSSet *)dataTypes completionHandler:(void (^)(NSArray *))completionHandler;
    
    - (void)removeDataOfTypes:(NSSet *)dataTypes forDataRecords:(NSArray *)dataRecords completionHandler:(void (^)(void))completionHandler;
    
    - (void)removeDataOfTypes:(NSSet *)websiteDataTypes modifiedSince:(NSDate *)date completionHandler:(void (^)(void))completionHandler;
    
    

    WKWebsiteDataStoreのWebデータはWKWebsiteDataRecordクラスとして保存されており、fetchDataRecordsOfTypeメソッドでdatastoreのrecordデータを取得できます.しかし、WKWebsiteDataRecordクラスのヘッダファイルを開くと、次のことがわかります.
    @interface WKWebsiteDataRecord : NSObject
    
    /*! @abstract The display name for the data record. This is usually the domain name. */
    @property (nonatomic, readonly, copy) NSString *displayName;
    
    /*! @abstract The various types of website data that exist for this data record. */
    @property (nonatomic, readonly, copy) NSSet *dataTypes;
    
    @end
    
  • 共有プロパティは、レコードデータのドメイン名とレコードに保存されているWebデータの2つです.この中に何が入っているのか見てみましょう.
  •   WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
        [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray * _Nonnull records) {
            for (WKWebsiteDataRecord *record  in records)
            {
                NSLog(@"++++++++++++++++%@",[record description]);
            }
        }];
    
  • の結果は、
  • として出力される.
  • だからWKWebsiteDataRecordではクッキーデータは何も手に入らない.

  • UIWebViewのCookieメカニズム
    UIWebViewは、Webページを参照すると、WebページのクッキーをNSHTTPCookieStorage標準コンテナに自動的に格納します.後続のアクセスでクッキーはrequestリクエストに自動的に持ち込まれます.例えば、NSHTTPCookieStorageにはCookie、name=aが格納されている.value=b;domain=y.qq.com;expires=Sat,02 May 2017 23:20:25 GMT; UIWebViewによってリクエストが開始されますhttp://y.qq.comを選択すると、要求ヘッダは自動的にクッキーを持ち、WKWebViewによって要求が開始され、要求ヘッダは自動的にクッキーを持ちません.
    初歩的な解決策
    実は主にしなければならないのは2つのステップだけで、1、Cookieを取得して、2、Cookieを注入します
    1、Cookieを取得する
  • WKWebViewのCookie格納容器WKWebsiteDataStoreはプライベートストレージであるため、ここからCookieを取得することはできないが、現在の方法は(1)ウェブサイトから戻ってきたresponse headerfieldsから取得することである.(2)jsを呼び出す方法でクッキーを取得する.
  • (1)Webサイトから返されたresponse headerfieldsから
  • を取得
  • cookieはhttp responeのheaderfieldsが存在するため、responeを得ることができるWKWebViewコールバックを見つけ、
  • を印刷する.
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(WKNavigationResponse *)navigationResponse decisionHandler:(void (^)(WKNavigationResponsePolicy))decisionHandler{
        NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
        NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
        //  wkwebview  cookie   1
        for (NSHTTPCookie *cookie in cookies) {
    //        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
            NSLog(@"wkwebview  cookie:%@", cookie);
     
        }
        //  wkwebview  cookie   2   Set-Cookie  
        NSString *cookieString = [[response allHeaderFields] valueForKey:@"Set-Cookie"];
        NSLog(@"wkwebview  cookie:%@", cookieString);
     
        //      NSHTTPCookieStorage   
        NSHTTPCookieStorage *cookieJar2 = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (NSHTTPCookie *cookie in cookieJar2.cookies) {
            NSLog(@"NSHTTPCookieStorage  cookie%@", cookie);
        }
        decisionHandler(WKNavigationResponsePolicyAllow);
    }
    
  • (2)jsを呼び出す方法によりクッキーを取得する.
  • - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
    {
        
        [webView evaluateJavaScript:[NSString stringWithFormat:@"document.cookie"] completionHandler:^(id _Nullable response, NSError * _Nullable error) {
            if (response != 0) {
                NSLog(@"





    document.cookie%@,%@",response,error); } }]; }
  • Cookieを取得中に遭遇するピット
  • 1、(2)のdocumentを通過する.クッキーの方法でクッキーを取得するには一定の実行可能性があるが,実践後に取得したクッキーは全面的ではなく,httponlyのクッキーを取得できないことが分かった.https://www.zhihu.comで試してみるとz_c 0のクッキーはhttponly属性のクッキーであるため取得できません.
  • 2、(1)方法でも(2)方法でも302リクエストのCookie問題は解決できないようです.例えば、サイトAにアクセスするとして、Aでログインをクリックし、ページをBアドレスにジャンプし、Bでログインが完了した後302でAサイトにジャンプします.このとき、クッキーはBアドレスのresponseに存在し、Aアドレスのresponseにはクッキーのフィールドは存在しない.しかし,Aアドレスのresponseしか取得できず,Bアドレスのresponseをキャプチャできない.そのため、このタイプのサイトのクッキーは取得できません.
  • 2、注入Cookie
  • 注入Cookieとは、以前にクッキーを保存していたNSHTTPCookieStorageから関連Cookieを取り出し、再度アクセスを要求したときにrequestにCookieを注入することである.Cookieの注入にも様々な方法があります.
  • (1)JS注入1
  • //   storage   cookie           
    NSArray *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];    
        NSMutableString *jscode_Cookie = [@"" mutableCopy];
        [tmp enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            NSLog(@"%@   =  %@", obj.name, obj.value);
            [jscode_Cookie appendString:[NSString stringWithFormat:@"document.cookie =  '%@=%@';", obj.name, obj.value]];
        }];
    
    WKUserContentController* userContentController = WKUserContentController.new;
        WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"" injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
        
        [userContentController addUserScript:cookieScript];
        WKWebViewConfiguration* webViewConfig = WKWebViewConfiguration.new;
        webViewConfig.userContentController = userContentController;
    WKWebView * webView = [[WKWebView alloc] initWithFrame:CGRectMake(/*set your values*/) configuration:webViewConfig];
    
  • (2)JS注入2
  • - (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
        [webView evaluateJavaScript:@"document.cookie ='TeskCookieKey1=TeskCookieValue1';" completionHandler:^(id result, NSError *error) {
            //...
        }];
    }
    
  • (3)NSMutableURLRequest注入Cookie
  • NSURL *url = request.URL;
    NSMutableString *cookies = [NSMutableString string];
    NSMutableURLRequest *requestObj = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
        
    NSArray *tmp = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
    NSDictionary *dicCookies = [NSHTTPCookie requestHeaderFieldsWithCookies:tmp];
    NSString *cookie = [self readCurrentCookie];
    [requestObj setValue:cookie forHTTPHeaderField:@"Cookie"];
    [_webView loadRequest:requestObj];
    
    
    -(NSString *)readCurrentCookie{
        NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
        NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
        NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (NSHTTPCookie *cookie in [cookieJar cookies]) {
            [cookieDic setObject:cookie.value forKey:cookie.name];
        }
        
        // cookie  ,         ,     
        for (NSString *key in cookieDic) {
              NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
                [cookieValue appendString:appendString];
        }
        return cookieValue;
    }
    
  • Cookie注入中に遭遇するピット
  • (1)JSが注入するCookie、例えばPHPコードはCookie容器から取り出せない、直接js document.クッキーは
    NSMutableURLRequestが注入したPHPなどの動的言語は直接$_からCOOKIEオブジェクトで取得した
    (2)app終了後のクッキー紛失の問題
  • 開発中にクッキーの保存に問題があり、クッキーを注入したときに一部のクッキーが失われたことが分かった.ネット上で別の解決策を見つけたのは、クッキーが期限切れになったということで、手動で保存時間を設定する必要があります.デフォルトでは、クッキーの保存時間はappが終了するとクッキーが消去されます.参照:http://www.jianshu.com/p/1e402922ee32/.クッキーの有効期限を設定した後も、クッキーの紛失の問題があります.(ため息
  • はその後NSUserDefaultを用いてクッキーを取得するときにクッキーを保存し、再アクセスするときにNSUserDefaultからクッキーを取り出して再設定することで、以前に保存していたクッキーを得るrequestに
  • を注入する.
    //     cookie  storage   
    
    - (void)webView:(WKWebView *)webView decidePolicyForNavigationResponse:(nonnull WKNavigationResponse *)navigationResponse decisionHandler:(nonnull void (^)(WKNavigationResponsePolicy))decisionHandler{
        NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
        NSArray *cookies =[NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
        
        NSString *cookieString = [[response allHeaderFields] valueForKey:@"Set-Cookie"];
        
        for (NSHTTPCookie *cookie in cookies) {
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
        }
        
    
        NSMutableArray *cookieArray = [[NSMutableArray alloc] init];
        for (NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
            [cookieArray addObject:cookie.name];
            NSMutableDictionary *cookieProperties = [NSMutableDictionary dictionary];
            [cookieProperties setObject:cookie.name forKey:NSHTTPCookieName];
            [cookieProperties setObject:cookie.value forKey:NSHTTPCookieValue];
            [cookieProperties setObject:cookie.domain forKey:NSHTTPCookieDomain];
            [cookieProperties setObject:cookie.path forKey:NSHTTPCookiePath];
            [cookieProperties setObject:[NSNumber numberWithInt:cookie.version] forKey:NSHTTPCookieVersion];
            
            [cookieProperties setObject:[[NSDate date] dateByAddingTimeInterval:2629743] forKey:NSHTTPCookieExpires];
            
            [[NSUserDefaults standardUserDefaults] setValue:cookieProperties forKey:cookie.name];
            [[NSUserDefaults standardUserDefaults] synchronize];
            
        }
        
        [[NSUserDefaults standardUserDefaults] setValue:cookieArray forKey:@"cookieArray"];
        [[NSUserDefaults standardUserDefaults] synchronize];
        
           
        //  WKWebView Cookie,    
        WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
        [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                         completionHandler:^(NSArray * __nonnull records) {
                             for (WKWebsiteDataRecord *record  in records)
                             {
                                 //                             if ( [record.displayName containsString:@"baidu"]) //    ,         ,     
                                 //                             {
                                 [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
                                                                           forDataRecords:@[record]
                                                                        completionHandler:^{
                                                                            NSLog(@"Cookies for %@ deleted successfully",record.displayName);
                                                                        }];
                                 //                             }
                             }  
                         }];
        
        decisionHandler(WKNavigationResponsePolicyAllow);
    }
    
    
    //    storage     cookie    
    -(NSString *)readCurrentCookie{
        
        NSMutableArray* cookieDictionary = [[NSUserDefaults standardUserDefaults] valueForKey:@"cookieArray"];
        NSLog(@"cookie dictionary found is %@",cookieDictionary);
        
        for (int i=0; i < cookieDictionary.count; i++)
        {
            NSLog(@"cookie found is %@",[cookieDictionary objectAtIndex:i]);
            NSMutableDictionary* cookieDictionary1 = [[NSUserDefaults standardUserDefaults] valueForKey:[cookieDictionary objectAtIndex:i]];
            NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:cookieDictionary1];
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
        }
    
        NSMutableDictionary *cookieDic = [NSMutableDictionary dictionary];
        NSMutableString *cookieValue = [NSMutableString stringWithFormat:@""];
        NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
        for (NSHTTPCookie *cookie in [cookieJar cookies]) {
            [cookieDic setObject:cookie.value forKey:cookie.name];
        }
        
        // cookie  ,         ,     
        for (NSString *key in cookieDic) {
    //        if ([key isEqualToString:@"nweb_qa"] || [key isEqualToString:@"z_c0"] || [key isEqualToString:@"_xsrf"]) {
                NSString *appendString = [NSString stringWithFormat:@"%@=%@;", key, [cookieDic valueForKey:key]];
                [cookieValue appendString:appendString];
    //        }
        }
        return cookieValue;
    }
    
    

    締めくくり
  • WKWebViewクッキーの同期問題には穴が多く、リダイレクトアドレスCookieを取得する問題はまだ解決されていません.アドバイスや困惑している学生はコメントしたり、プライベートチャットしたりして解決することができます.