iOS 瀏覽器的 Cookie 同步思路

前言

本文主要記錄下研究瀏覽器的網絡攔截中, 瀏覽器是如何同步 WkWebView 和原生的 cookie 。瀏覽器

爲何須要同步 cookies?

在 WebKit 內核中,網絡請求是在一個獨立的進程中進行的,WebKit 內核中有一套本身的 cookie 機制。在 iOS 原生也有一套 cookie 的管理機制 NSHTTPCookieStorage。因爲 WebKit cookie 和 原生 cookie 是 2 套相互獨立的機制,他們之間的 cookie 並不一樣步。當咱們經過 NSURLProtol 攔截 WebKit 的網絡請求時,網絡請求所產生的相關 cookie,就都存儲在 NSHTTPCookieStorage,而 NSHTTPCookieStorage 並不會和 WebKit 的 cookie 進行同步,這就致使了在 js 會沒法經過 document.cookie 獲取到相關的 cookie。而且, js 設置的 cookie 是存儲在 WebKit cookie 中,原生髮起網絡請求時,也獲取不到這部分 cookie。cookie

所以,咱們須要經過某種機制,將 WebKit cookies 和 NSHTTPCookieStorage 中的 cookies 進行同步。網絡

瀏覽器的 Cookie 同步思路

同步 WebKit cookie 到 原生 cookie

經過研究 瀏覽器的 cookie 同步的 js 代碼,發現其主要是經過 hook js 中的 Documment.cookie 的 set 和 get 方法進行同步。具體代碼以下:async

Object.defineProperty(document, 'cookie', {
    get: function() {
        // 從 cookie list 中取出 cookie
        var cookieList = cookieNameAndValueList();
        return cookieList;
    },
    set: function(cookieString) {
        if (typeof cookieString !== 'string') {
            return;
        }
        // 將 cookie 存儲到 cookie List 中
        getCookieAndAddToCookieListFromString(cookieString);
        var message = {};
        message['cookie'] = cookieString;
        // 將 cookie 發送給原生
        __B_Cookie_Handle__.postMessage(message);
    },
    configurable: true
});
複製代碼

總體思路是經過 hook Documment.cookie 的 set 和 get 方法,本身維護一個 cookie 列表,當設置新的 cookie 時,會將新的 cookie 存儲在本身維護的 cookie 列表中,並經過 postMessage 將 cookie 同步給原生。當獲取 cookie 時,會從本身維護的 cookie 列表中取值返回。post

同步原生 cookie 到 WebKit cookie

瀏覽器會檢查到原生的 HTTP response 的 header ,若是有 Set-Cookie 字段,會經過通知的方式,調用 [xxxWebView bSetCookie:] 方法,在該方法中,會執行下列的 js 語句ui

if (window.b_Notify){
    b_Notify('seewo.com','csrfToken=WXcGJ7BxBBNb05gDICx6KNk8; path=/')
}
複製代碼

調用了 b_Notify 方法,將 cookie 同步給 WebKit 中,而 b_Notify 的具體實現以下:url

window['b_Notify'] = function(host, cookieString) {
    // 判斷是否數據同一個域名下的 cookie 
    if (isSuffix(document.location.hostname, host)) {
        if (typeof cookieString == 'string') {
            // 若是有多個 cookie,會根據 , 分割
            var cookieStringList = cookieString.split(',');
            var length = cookieStringList.length;
            if (length > 0) {
                for (var index = 0; index < length; index++) {
                    var cookieString = cookieStringList[index];
                    // 因爲 cookie 的 Expires 設置爲 Expires=Wed, 21 Oct 2015 07:28:00 GMT; 裏面也包含了了 「,」,
                    //因此若是判斷匹配到的 cookieString 裏面包含了 Sun 等字段,則將當前字段和下一個字段進行組合,造成一個完整的 cookie 
                    if (cookieString.match(/=Sun|=Mon|=Tue|=Wed|=Thu|=Fri|=Sat/) == null && (index < length - 1)) {
                        // 將 cookie String 轉換成 obj 後,保存到本身維護的 cookie list 中
                        saveCookieStringToList(cookieString);
                    } else {
                        saveCookieStringToList(cookieString + ',' + cookieStringList[index + 1]);
                        index++;
                    }
                }
            }
        }
    }

    // 將 cookie 同步給全部的 subFrame
    var subFrames = document.querySelectorAll('iframe');
    if (!subFrames) {
        return;
    }
    var length = subFrames.length;
    var index;
    if (length > 0) {
        for (index = 0; index < length; index++) {
            var message = {};
            message['syss_info'] = host;
            message['sck_info'] = cookieString;
            subFrames[index].contentWindow.postMessage(message, '*');
        }
    }
};
複製代碼

b_Notify 的會分析從原生獲取到的 cookie string,而後保存在本身維護的 cookie 列表。並將 cookie 同步給全部的 subFrame。spa

綜上所訴,在 瀏覽器中,經過 hook document set 方法的方式,將在 js 中設置的 cookie 同步到 原生上。 經過在 js 中注入 b_Notify 方法,當原生監聽到請求的 response 中帶有 Set-Cookie 字段時,原生直接調用已經注入的 b_Notify 方法,將 cookie 同步給 Webkit。code

manFrame 的 cookie 同步

經過上面的解析,WebKit cookie 和原生的 cookie 已經可以同步了。可是還有一個邊界條件缺乏考慮。 咱們先理清一下整個流程。以下圖所示:regexp

image.png

從上圖中咱們能夠看到,當若是 main frame 的 response 是帶有 set-Cookie 字段時,按照邏輯,會嘗試調用 b_Notify,可是因爲此時 main frame 尚未被 WebKit 加載, b_Notify 實際上尚未注入到 js 中,因此此時是沒有辦法經過 b_Notify 將原生 cookie 同步給 WebKit,爲了解決這個問題, 瀏覽器在 cookie 同步相關的 js 執行時,在 js 層會主動發起一個特殊的網絡請求,原生攔截到這個特殊的網絡請求後,會返回這個特殊網絡請求中指定的 url 的 cookie,具體代碼以下:

function() {

    function asyncGetCookieFromNative(A) {
        /** * 忽略部分代碼 */
    }
    function syncGetCookieFromNative(B) {
        var reqeust = new XMLHttpRequest();
        if (reqeust != null) {
            reqeust.open('GET', B, false);
            reqeust.send();
            if (reqeust.status == 200) {
                var responseText = reqeust.responseText;
                if (typeof responseText == 'undefined' || responseText.length == 0) {
                    return;
                }
                var cookieList = JSON.parse(responseText);
                if (typeof cookieList === 'object') {
                    addCookieList(cookieList);
                }
            }
        }
    }
    function getCookieFromNative(isAsync) {
        var protocol = document.location.protocol;
        var host = document.location.host;
        if (typeof protocol !== 'string' || typeof host !== 'string') {
            return;
        }
        // 建立一個特殊的網絡請求,網絡請求後面攜帶了當前的網頁的 href 地址
        if (isAsync) {
            asyncGetCookieFromNative(protocol + '//' + host + '/9CB4F2575FDD4C5BA05A63E96FC96E70/?' + document.location.href);
        } else {
            syncGetCookieFromNative(protocol + '//' + host + '/9CB4F2575FDD4C5BA05A63E96FC96E70/?' + document.location.href);
        }
    }

    /** * 忽略部分代碼 */
    getCookieFromNative(false);
} ()

複製代碼

因爲原生會攔截 WebKit 的全部網絡請求,因此當 WebKit 發起的帶有特殊字符(9CB4F2575FDD4C5BA05A63E96FC96E70)被原生攔截到時,原生能夠直接獲取到 search 中的 url,經過 url 在 NSHTTPCookieStorage 中獲取到 cookies 後,在經過 response 返回給 Webkit。從而達到 WebKit 主動向原生拉取 cookie 的目的。

相關文章
相關標籤/搜索