這才是 WKWebview Cookie 管理的正確方式

說起 WKWebview 代替 UIWebview 帶來的好處你可以舉出一堆堆的例子情萤,但說到 WKWebview 的問題,你繞不過的就是 WKWebview cookie 和 NSHTTPCookieStorage cookie 不共享的問題候齿。你可以在網(wǎng)絡(luò)上搜到如何將他們相互同步的帖子。

如何將 NSHTTPCookieStorage 同步給 WKWebview ,大概要處理很多種情況妻献,包括但不限于以下;

  1. 初次加載頁面時(shí)团赁,同步 cookie 到 WKWebview
  2. 處理 ajax 請(qǐng)求時(shí)育拨,需要的 cookie
  3. 如果 response 里有 set-cookie 還需要緩存這些 cookie
  4. 如果是 302 還需要處理 cookie 傳遞的問題

所以,如果你按照上面的要求編寫了代碼欢摄,你會(huì)發(fā)現(xiàn)總有漏網(wǎng)之魚的情況沒有處理熬丧,比方說請(qǐng)求 response 設(shè)置了 cookie,為了在后續(xù)跳轉(zhuǎn)中帶上這些 cookie怀挠,你需要暫存下來析蝴,這樣可能會(huì)污染到 NSHTTPCookieStorage ;再舉一個(gè)極端的真實(shí)的案例绿淋,如果有個(gè)網(wǎng)站的鑒權(quán)是通過 302 鑒權(quán) 和 response set-cookie 的闷畸,那么你會(huì)發(fā)現(xiàn)這個(gè)網(wǎng)站在鑒權(quán)那里陷入了死循環(huán),因?yàn)?302 response set-cookie 后 302 的 location 地址加載時(shí)并沒有攜帶上 302 時(shí)設(shè)置的 cookie吞滞,進(jìn)而繼續(xù) 302 set-cookie的跳轉(zhuǎn)腾啥。

那如果解決 302 response set-cookie 的問題,我們不能在上述方案里修修補(bǔ)補(bǔ)冯吓,上述方案對(duì)正常的數(shù)據(jù)請(qǐng)求已經(jīng)有很大的侵入性倘待,對(duì)很多沒有必要進(jìn)行 cookie 設(shè)置的頁面做了處理,一定程度上對(duì)性能也有影響组贺。讓我們跳脫原來的方案凸舵,重新審視下 WKWebview cookie 相關(guān)的資料。

WKWebview cookie 是怎么存儲(chǔ)的

  1. session 級(jí)別的 cookie
    session 級(jí)別的 cookie 是保存在 WKProcessPool 里的失尖,每個(gè) WKWebview 都可以關(guān)聯(lián)一個(gè) WKProcessPool 的實(shí)例啊奄,如果需要在整個(gè) App 生命周期里訪問 h5 保留 h5 里的登錄狀態(tài)的,可以將使用 WKProcessPool 的單例來共享登錄狀態(tài)掀潮。

WKProcessPool 是個(gè)沒有屬性和方法的對(duì)象菇夸,唯一的作用就是標(biāo)識(shí)是不是需要新的 session 級(jí)別的管理對(duì)象,一個(gè)實(shí)例代表一個(gè)對(duì)象仪吧。

  1. 未過期的 cookie
    有有效期的 cookie 被持久化存儲(chǔ)在 NSLibraryDirectory 目錄下的 Cookies/文件夾庄新。
    image.png

注意,cookie 持久化文件地址在 iOS 9+ 上在 /Users/Mac/Library/Developer/CoreSimulator/Devices/D2F74420-D59B-4A15-A50B-774D3D01FADE/data/Containers/Data/Application/E8646AD5-1110-43F3-95D9-DE6A32E78DB7/Library/Cookies.
但是在 iOS 8 上 cookie 被保存在兩部分,一部分如上所述择诈,還有一部分保存在 App 無法獲取的地方械蹋,/Users/Mac/Library/Developer/CoreSimulator/Devices/D2F74420-D59B-4A15-A50B-774D3D01FADE/data/Library/Cookies,大概就是后者的 Cookie 是 iOS 的 Safari 使用 羞芍。

在 Cookies 目錄下兩個(gè)文件比較重要;

  • Cookie.binarycookies
  • <appid>.binarycookies
    兩者的區(qū)別是 <appid>.binarycookies 是 NSHTTPCookieStorage 文件對(duì)象哗戈;Cookie.binarycookies 則是 WKWebview 的實(shí)例化對(duì)象。
    這也是為什么 WKWebview 和 NSHTTPCookieStorage 的原因——因?yàn)楸槐4嬖诓煌奈募?dāng)中荷科。

為了驗(yàn)證唯咬,你可以打開這兩者文件進(jìn)行查看,這里不再展開畏浆。

當(dāng)然兩個(gè)文件都是 binary file胆胰,直接用文本瀏覽器打開是看不到,有一個(gè) python 寫的腳本 BinaryCookieReaderhttps://gist.github.com/sh1n0b1/4bb8b737370bfe5f5ab8全度。可以讀出來

WKWebview Cookie 是如何工作的斥滤?

  1. 當(dāng) webview loadRequest 或者 302 或者在 webview 加載完畢将鸵,觸發(fā)了 ajax 請(qǐng)求時(shí)伦籍,WKWebview 所需的 Cookie 會(huì)去 Cookie.binarycookies 里讀取本域名下的 Cookie 子房,加上
    WKProcessPool持有的 Cookie 一起作為 request 頭里的 Cookie 數(shù)據(jù)镰惦。
  2. 但是如果仔細(xì)查看 NSURLRequest.h 源代碼拯杠,而不是僅僅查看NSDictionary<NSString *, NSString *> *allHTTPHeaderFields; 的 quick help唇礁,你會(huì)發(fā)現(xiàn)這句話号醉;
    @abstract Sets the HTTP header fields of the receiver to the given
    dictionary.
    @discussion This method replaces all header fields that may have
    existed before this method call.    

再查看下HTTPShouldHandleCookies 的 quick help悉抵,

@property BOOL HTTPShouldHandleCookies;
Description 
A boolean value that indicates whether the receiver should use the default cookie handling for the request.
YES if the receiver should use the default cookie handling for the request, NO otherwise. The default is YES.
If your app sets the Cookie header on an NSMutableURLRequest object, then this method has no effect, and the cookie data you set in the header overrides all cookies from the cookie store.
SDKs    iOS 8.0+, macOS 10.10+, tvOS 9.0+, watchOS 2.0+

結(jié)合兩者岭皂,你也會(huì)發(fā)現(xiàn)一個(gè)核心的概念-如果設(shè)置了 allHTTPHeaderFields茬贵,則不用使用 the cookie manager by default簿透。

所以我們的方案是-在頁面加載過程中不去設(shè)置 allHTTPHeaderFields,全部使用默認(rèn) Cookie mananger 管理解藻,這樣就不會(huì)有 Cookie 污染也不會(huì)有 302 Cookie 丟失的問題了老充,下面讓我們驗(yàn)證一下。

唯一的問題——如何將 NSHTTPCookieStorage 的 Cookie 共享給 WKWebview螟左。

解決方案

在首次加載 url 時(shí)啡浊,檢查是否已經(jīng)同步過 Cookie。如果沒有同步過胶背,則先加載 一個(gè) cookieWebivew巷嚣,它的主要目的就是將 Cookie 先使用 usercontroller 的方式寫到 WKWebview 里,這樣在處理正式的請(qǐng)求時(shí)钳吟,就會(huì)帶上我們從 NSHTTPCookieStorage 獲取到的 Cookie了廷粒。
核心代碼如下,

if ([AppHostCookie loginCookieHasBeenSynced] == NO) {
        //
        NSURL *cookieURL = [NSURL URLWithString:kFakeCookieWebPageURLString];
        NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:cookieURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:120];
        WKWebView *cookieWebview = [self getCookieWebview];
        [self.view addSubview:cookieWebview];
        [cookieWebview loadRequest:mutableRequest];
        DDLogInfo(@"[JSBridge] preload cookie for url = %@", self.loadUrl);
    } else {
        [self loadWebPage];
    }
//  注意红且,CookieWebview 和 正常的 webview 是不同的
- (WKWebView *)getCookieWebview
{
    // 設(shè)置加載頁面完畢后评雌,里面的后續(xù)請(qǐng)求树枫,如 xhr 請(qǐng)求使用的cookie
    WKUserContentController *userContentController = [WKUserContentController new];

    WKWebViewConfiguration *webViewConfig = [[WKWebViewConfiguration alloc] init];
    webViewConfig.userContentController = userContentController;

    webViewConfig.processPool = [AppHostCookie sharedPoolManager];
    
    NSMutableArray<NSString *> *oldCookies = [AppHostCookie cookieJavaScriptArray];
    [oldCookies enumerateObjectsUsingBlock:^(NSString *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
        NSString *setCookie = [NSString stringWithFormat:@"document.cookie='%@';", obj];
        WKUserScript *cookieScript = [[WKUserScript alloc] initWithSource:setCookie injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:YES];
        [userContentController addUserScript:cookieScript];
    }];

    WKWebView *webview = [[WKWebView alloc] initWithFrame:CGRectMake(0, -1, SCREEN_WIDTH,ONE_PIXEL) configuration:webViewConfig];

    webview.navigationDelegate = self;
    webview.UIDelegate = self;

    return webview;
}

這里需要處理的問題是,加載完畢或者失敗后需要清理舊 webview 和設(shè)置標(biāo)記位景东。

static NSString * _Nonnull kFakeCookieWebPageURLString = @"http://ai.api.com/xhr/user/getUid.do?26u-KQa-fKQ-3BD"
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation
{

    NSURL *targetURL = webView.URL;
    if ([AppHostCookie loginCookieHasBeenSynced] == NO && targetURL.query.length > 0 && [kFakeCookieWebPageURLString containsString:targetURL.query]) {
        [AppHostCookie setLoginCookieHasBeenSynced:YES];
        // 加載真正的頁面砂轻;此時(shí)已經(jīng)有 App 的 cookie 存在了。
        [webView removeFromSuperview];
        [self loadWebPage];
        return;
    }
}

同時(shí)記得刪掉原來對(duì) webview 的 Cookie 的所有處理的代碼斤吐。

處理至此搔涝,大功告成,這樣的后續(xù)請(qǐng)求和措, WKWebview 都用自身所有的 Cookie 和 NSHTTPCookieStorage 的 Cookie庄呈,這樣既達(dá)到了 Cookie 共享的目的, WKWebview 和 NSHTTPCookieStorage 的 Cookie 也做了個(gè)隔離派阱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诬留,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子贫母,更是在濱河造成了極大的恐慌文兑,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件腺劣,死亡現(xiàn)場(chǎng)離奇詭異绿贞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)橘原,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門籍铁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人趾断,你說我怎么就攤上這事拒名。” “怎么了芋酌?”我有些...
    開封第一講書人閱讀 162,823評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵靡狞,是天一觀的道長。 經(jīng)常有香客問我隔嫡,道長甸怕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評(píng)論 1 292
  • 正文 為了忘掉前任腮恩,我火速辦了婚禮梢杭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘秸滴。我一直安慰自己武契,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著咒唆,像睡著了一般届垫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上全释,一...
    開封第一講書人閱讀 51,190評(píng)論 1 299
  • 那天装处,我揣著相機(jī)與錄音,去河邊找鬼浸船。 笑死妄迁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的李命。 我是一名探鬼主播登淘,決...
    沈念sama閱讀 40,078評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼封字!你這毒婦竟也來了黔州?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,923評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤阔籽,失蹤者是張志新(化名)和其女友劉穎流妻,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仿耽,經(jīng)...
    沈念sama閱讀 45,334評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡合冀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評(píng)論 2 333
  • 正文 我和宋清朗相戀三年各薇,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了项贺。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡峭判,死狀恐怖开缎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情林螃,我是刑警寧澤奕删,帶...
    沈念sama閱讀 35,428評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站疗认,受9級(jí)特大地震影響完残,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜横漏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評(píng)論 3 326
  • 文/蒙蒙 一谨设、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缎浇,春花似錦扎拣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽誉券。三九已至,卻和暖如春刊愚,著一層夾襖步出監(jiān)牢的瞬間踊跟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評(píng)論 1 269
  • 我被黑心中介騙來泰國打工百拓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琴锭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,734評(píng)論 2 368
  • 正文 我出身青樓衙传,卻偏偏與公主長得像决帖,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蓖捶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容