本文主要記錄下研究瀏覽器的網絡攔截中, 瀏覽器是如何同步 WkWebView 和原生的 cookie 。瀏覽器
在 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 同步的 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
瀏覽器會檢查到原生的 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
經過上面的解析,WebKit cookie 和原生的 cookie 已經可以同步了。可是還有一個邊界條件缺乏考慮。 咱們先理清一下整個流程。以下圖所示:regexp
從上圖中咱們能夠看到,當若是 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 的目的。