WKWebView的Cookie問題小記

往者不可諫,來者猶可追html

原文連接git

1、Cookie和Session概述

Cookie和Session都是爲了保存客戶端和服務端之間的交互狀態,實現機制不一樣,各有優缺點。github

一、Cookie
  • Cookie是客戶端請求服務端時,服務器會將一些信息以鍵值對的形式返回給客戶端,保存在瀏覽器中,後續交互的時候能夠帶上這些Cookie值。用Cookie就能夠方便的作一些緩存。
  • Cookie的缺點是大小和數量都有限制;Cookie是存在客戶端的可能被禁用、刪除、篡改,是不安全的;Cookie若是很大,每次要請求都要帶上,這樣就影響了傳輸效率。
  • Cookie的內容主要包括:名字過時時間路徑路徑一塊兒構成Cookie的做用範圍。若不設置過時時間,則表示這個Cookie的生命期爲瀏覽器會話期間,關閉瀏覽器窗口,Cookie就消失。這種生命期爲瀏覽器會話期的Cookie被稱爲會話Cookie會話Cookie通常不存儲在硬盤上而是保存在內存裏。
  • 若設置了過時時間,瀏覽器就會把Cookie保存到硬盤上,關閉後再次打開瀏覽器,這些Cookie仍然有效直到超過設定的過時時間。存儲在硬盤上的Cookie能夠在不一樣的瀏覽器進程間共享,好比兩個IE窗口。而對於保存在內存裏的Cookie,不一樣的瀏覽器有不一樣的處理方式 。
二、Session
  • Session是基於Cookie來實現的,不一樣的是Session自己存在於服務端,可是每次傳輸的時候不會將數據傳輸給客戶端,只是把表明一個客戶端的sessionid(jsessionid只是Tomcat中對sessionid的叫法)寫在客戶端的Cookie中,這樣每次傳輸這個ID就能夠了。web

  • Session的優點就是傳輸數據量小,比較安全。Session有缺點,就是若是Session不作特殊的處理容易失效、過時、丟失或者Session過多致使服務器內存溢出,而且要實現一個穩定可用安全的分佈式Session框架也是有必定複雜度的。在實際使用中就要結合Cookie和Session的優缺點針對不一樣的問題來設計解決方案。objective-c

三、理解
  • Session是一種服務器端的機制,服務器使用一種相似於散列表的結構(也可能就是使用散列表)來保存信息。
  • 當Server程序要爲某個客戶端的請求建立一個session時,服務器首先檢查這個客戶端的請求裏是否已包含了一個session id,若是已包含則說明之前已經爲此客戶端建立過session,服務器就按照session id把這個session檢索出來使用(檢索不到,會新建一個);若是客戶端請求不包含session id,則爲此客戶端建立一個session而且生成一個與此session相關聯的session id,session id的值應該是一個既不會重複,又不容易被找到規律以仿造的字符串,這個session id將被在本次響應中返回給客戶端保存。
  • 保存這個session id的方式能夠採用Cookie,這樣在交互過程當中瀏覽器能夠自動的按照規則把這個標識發送給服務器。通常這個cookie的名字都是相似於SEEESIONID。但Cookie能夠被人爲的禁止,則必須有其餘機制以便在cookie被禁止時仍然可以把session id傳遞迴服務器
  • 常常被使用的一種技術叫作URL重寫,就是把session id直接附加在URL路徑的後面。還有一種技術叫作表單隱藏字段。就是服務器會自動修改表單,添加一個隱藏字段,以便在表單提交時可以把session id傳遞迴服務器。

2、WKWebView和Cookie

一、原由
WKWebView 發起的請求不會自動帶上存儲於 NSHTTPCookieStorage 容器中的 Cookie
複製代碼
  • 目前許多 H5 業務都依賴於 Cookie 做登陸態校驗,若是登錄是在 WebView 裏作的,不會有什麼問題;可是在不少場景下,在Native作登陸,須要將登陸信息帶給WebView;可是在Native作了登陸,也獲取了Cookie信息,也使用 NSHTTPCookieStorage 將Cookie存到了本地;可是WKWebView在打開時候,不會自動去NSHTTPCookieStorage獲取Cookie信息,這就是著名的首次 WKWebView 請求不攜帶 Cookie 的問題跨域

  • WKWebView 實例其實會將 Cookie 存儲於 NSHTTPCookieStorage 中,但存儲時機有延遲,在iOS 8上,當頁面跳轉的時候,當前頁面的 Cookie 會寫入 NSHTTPCookieStorage 中,而在 iOS 10 上,JS 執行 document.cookie 或服務器 set-cookie 注入的 Cookie 會很快同步到 NSHTTPCookieStorage 中。瀏覽器

  • 其實,iOS11 能夠解決首次 WKWebView 請求不攜帶 Cookie 的問題只要是存在 WKHTTPCookieStore 裏的 cookie,WKWebView 每次請求都會攜帶緩存

二、獲取Cookie
// 方法一
    NSHTTPURLResponse *response = (NSHTTPURLResponse *)navigationResponse.response;
    NSArray *cookies = [NSHTTPCookie cookiesWithResponseHeaderFields:[response allHeaderFields] forURL:response.URL];
    NSLog(@"response-cookies = %@",cookies);
    
    //方法二
    NSString *cookieString = [[response allHeaderFields] valueForKey:@"Set-Cookie"];
    NSLog(@"cookieString = %@",cookieString);
    
    //方法三(若是有的話)
    NSArray<NSHTTPCookie *> *httpCookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
    NSLog(@"httpCookies = %@",httpCookies);
    
    //方法四
    if(@available(iOS 11, *)){
        //WKHTTPCookieStore的使用
        WKHTTPCookieStore *cookieStore = self.wkWebView.configuration.websiteDataStore.httpCookieStore;
        //獲取 cookies
        [cookieStore getAllCookies:^(NSArray<NSHTTPCookie *> * _Nonnull cookies) {
            [cookies enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
                NSLog(@"cookieStore-cookies_%@:%@",@(idx),obj);
            }];
        }];
    }
    //將cookie設置到本地
    for (NSHTTPCookie *cookie in cookies) {
        //NSHTTPCookie cookie
        [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookie:cookie];
    }
    
    decisionHandler(WKNavigationResponsePolicyAllow);
}
複製代碼

查看WKHTTPCookieStore中的某個cookie信息,以下安全

version:1
name:Hm_lvt_0c0e9d9b1e7d617b3e6842e85b9fb068
value:1554970993,1554971029,1554971246,1554971319
expiresDate:'2020-04-10 08:28:38 +0000'
created:'2019-04-11 08:28:38 +0000'
sessionOnly:FALSE
domain:.jianshu.com
partition:none
sameSite:none
path:/
isSecure:FALSE
path:"/" 
isSecure:FALSE
複製代碼
三、未過時Cookie持久化
  • 未過時的 Cookie被持久化存儲在 NSLibraryDirectory 目錄下的 Cookies/文件夾。bash

  • Cookie 持久化文件地址在 iOS 9+ 上在NSLibraryDirectory/Cookies,可是在 iOS 8 上 cookie 被保存在兩部分,一部分如上所述,還有一部分保存在 App 沒法獲取的地方,/Users/Mac/Library/Developer/CoreSimulator/Devices/D2F74420-D59B-4A15-A50B-774D3D01FADE/data/Library/Cookies,大概就是後者的 Cookie 是 iOS 的 Safari 使用 。

  • 在 Cookies 目錄下兩個文件比較重要;

    Cookies.binarycookies
    <appid>.binarycookies
    複製代碼

    二者的區別是 .binarycookies 是 NSHTTPCookieStorage 文件對象;.binarycookies 對應 WKWebview 的實例化對象。

3、WKWebView的Cookie注入

一、Javascript注入Cookie

在初始化 WKWebView 的時候,經過 WKUserScript 設置,使用Javascript 注入 Cookie

//js注入
WKUserContentController* userContentController = [[WKUserContentController alloc]init]; 
WKUserScript * cookieScript = [[WKUserScript alloc] initWithSource: @"document.cookie ='CookieKey=CookieValue';"injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];  
[userContentController addUserScript:cookieScript]; 

WKWebViewConfiguration* webViewConfig = [[WKWebViewConfiguration alloc]init]; 
webViewConfig.userContentController = userContentController; 
WKWebView *webView = [[WKWebView alloc] initWithFrame:frame configuration:webViewConfig];
複製代碼
  • 經過 document.cookie 設置 Cookie (JS注入)解決後續頁面(同域)Ajax、iframe 請求的 Cookie 問題;可是會遇到跨域丟失的問題,
  • 沒法解決302請求的Cookie問題,假設第一個請求是 www.a.com,咱們經過在 request header 裏帶上 Cookie 解決該請求的 Cookie 問題,接着頁面302跳轉到 www.b.com,這個時候 www.b.com 這個請求就可能由於沒有攜帶 cookie 而沒法訪問。
  • 每一次頁面跳轉前都會調用回調函數decidePolicyForNavigationAction, 在這裏攔截302請求,copy request,在 request header 中帶上 cookie 並從新 loadRequest。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler{
    //
}
複製代碼
  • 可是這種方法依然解決不了頁面 iframe 跨域請求的 Cookie 問題,畢竟-[WKWebView loadRequest:]只適合加載 mainFrame 請求。
二、 NSMutableURLRequest 請求帶上 Cookie
//request攜帶
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]]; 
//[request setHTTPShouldHandleCookies:YES];
[request setValue:[NSString stringWithFormat:@"%@=%@",@"CookieKey", @"CookieValue"] forHTTPHeaderField:@"Cookie"]; 
[webView loadRequest:request];       
複製代碼

說明:WKWebView loadRequest 前,在 request header 中設置 Cookie,能夠解決(首個)請求 Cookie 帶不上的問題;

三、WKHTTPCookieStore (iOS 11 later)
  • 利用iOS11 API WKHTTPCookieStore 解決 WKWebView 首次請求不攜帶 Cookie 的問題;這是由於:WKWebView每次請求都會攜帶 WKHTTPCookieStore 裏的 Cookie。(WKWebView 使用 NSURLProtocol 攔截請求沒法獲取 Cookie 信息)
  • 在執行 [WKWebView loadRequest:] 前將 NSHTTPCookieStorage中的Cookie信息複製到 WKHTTPCookieStore 中,以此來達到 WKWebView中注入Cookie 的目的。示例代碼以下:
if(@available(iOS 11, *)){
        //發送請求前插入cookie
    NSArray *cookies = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
    WKHTTPCookieStore *cookieStore = self.wkWebView.configuration.websiteDataStore.httpCookieStore;
    for (NSHTTPCookie *cookie in cookies) {
        [cookieStore setCookie:cookie completionHandler:^{
            //
        }];
    }
    [self.wkWebView loadRequest:request];
}
複製代碼
四、多WKWebView實例共享Cookie
  • Session 級別的 cookie 是保存在 WKProcessPool 裏的,每一個 WKWebview 均可以關聯一個 WKProcessPool 的實例,若是須要在整個 App 生命週期裏訪問 h5 保留 h5 裏的登陸狀態的,能夠將使用 WKProcessPool 的單例來共享登陸狀態。

  • 讓全部 WKWebView 共享同一個 WKProcessPool 實例,能夠實現多個 WKWebView 之間共享 Cookie(session Cookie and persistent Cookie) 數據。不過 WKWebView WKProcessPool 實例在 App 殺進程重啓後會被重置,致使 WKProcessPool 中的 Cookiesession Cookie 數據丟失,目前也沒法實現 WKProcessPool 實例本地化保存。

//WKProcessPool+SharedProcessPool.h
@interface WKProcessPool (SharedProcessPool)

+ (WKProcessPool*)sharedProcessPool;

@end

//WKProcessPool+SharedProcessPool.m
@implementation WKProcessPool (SharedProcessPool)

+ (WKProcessPool*)sharedProcessPool {
    static WKProcessPool* shared;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[WKProcessPool alloc] init];
    });
    return shared;
}
@end
    
//use
config.processPool = [WKProcessPool sharedProcessPool];    
self.wkWebView = [[WKWebView alloc]initWithFrame:self.view.bounds configuration:config];
[self.view addSubview:self.wkWebView];    
複製代碼
五、其餘
  • H5地址是非Https,遇到奇怪的Cookie丟失問題。緣由未知。

4、WKWebView中Cookie的清除

一、按內容刪除
if (@available(iOS 9, *)){
        // 以www.baidu.com爲例,是否包含baidu.com
        NSString *displayName = @"baidu.com";
        WKWebsiteDataStore *dataStore = [WKWebsiteDataStore defaultDataStore];
        [dataStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes] completionHandler:^(NSArray<WKWebsiteDataRecord *> * __nonnull records) {
            for (WKWebsiteDataRecord *record  in records){
                if ([displayName containsString:record.displayName]){
                    [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes forDataRecords:@[record] completionHandler:^{
                        NSLog(@"Cookies for %@ deleted successfully",record.displayName);
                    }];
                }
            }
        }];
    }
複製代碼
二、按時間刪除
- (void)removeWebViewDataCache:(NSDate *)sinceDate {
    
    if (@available(iOS 11.0, *)) {
        // iOS 9 之後終於可使用 WKWebsiteDataStore 來清理緩存
        NSSet *websiteDataTypes = [WKWebsiteDataStore allWebsiteDataTypes];
        [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:websiteDataTypes modifiedSince:sinceDate completionHandler:^{
            NSLog(@"clear webView cache");
        }];
    } else {
        // iOS 8 能夠經過清理 Library 目錄下的 Cookies 目錄來清除緩存
        NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
        NSString *cookiesFolderPath = [libraryPath stringByAppendingString:@"/Cookies"];
        [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:nil];
    }
}
複製代碼

5、IP 直連方案對Cookie的影響

一、 存在的問題
  • 採用 IP 直連方案後,服務端返回的 Cookie 裏的 Domain 字段也會使用 IP 。若是 IP 是動態的,就有可能致使一些問題:因爲許多 H5 業務都依賴於 Cookie 做登陸態校驗,而 WKWebView 上請求不會自動攜帶 Cookie。
二、解決問題辦法

6、推薦參考

相關文章
相關標籤/搜索