基於Chrome外掛實現支援CORS的本地開發代理

琰玉發表於2019-03-03

什麼是跨域

跨域:由於瀏覽器的同源策略,屬於不同域的頁面之間不能相互訪問各自的頁面內容。

開發中後臺專案,我們首先需要讓本地服務run起來,起一個如http://localhost:8000/scg/show這樣的頁面,然後,當我們需要進行網路互動時,通常使用相對域名,如/scg/search.json?pageNo=1, 假設專案的代理配置如下

undefined

那麼當請求/scg/search.json?pageNo=1時,代理轉發出去的請求的是http://ottscg.alibaba.net/scg/search.json?pageNo=1, localhost去請求ottscg.alibaba.net中的內容,這時就發生了跨域。

CORS跨域傳送 Cookie

瀏覽器通過代理髮出CORS請求時,會在頭資訊之中,增加一個Origin欄位,表示本次請求來自哪個源(協議 + 域名 + 埠)。伺服器根據這個值,決定是否同意這次請求。如果Origin指定的源,不在許可範圍內,伺服器會返回一個正常的HTTP回應。瀏覽器發現,這個回應的頭資訊沒有包含Access-Control-Allow-Origin欄位,就知道出錯了,從而丟擲一個錯誤,被XMLHttpRequest的onerror回撥函式捕獲。

如果Origin指定的域名在許可範圍內,伺服器返回的響應,會多出幾個以”Access-Control”開頭的頭資訊欄位,為了傳送帶cookie的跨域請求,我們首先需要關心的是Access-Control-Allow-OriginAccess-Control-Allow-Credentials

Access-Control-Allow-Origin

這個頭是允許CORS時,必須返回的頭,它的值要麼是請求時Origin欄位的值,要麼是一個*,表示接受任意域名的請求。

Access-Control-Allow-Credentials

預設情況下瀏覽器對跨域請求不會攜帶 Cookie,但鑑於 Cookie 在身份驗證等方面的重要性, CORS 推薦使用額外的響應頭欄位來允許跨域傳送 Cookie。
這是一個可選的頭,若傳送帶cookie的請求,則必須返回這個頭,並設定為true,表示伺服器允許客戶端傳送Cookie。
  需要注意的是,這個頭被設定為true時,Access-Control-Allow-Origin不允許使用*,而且只能指定單一域名,否則瀏覽器會報如下錯誤。

undefined

綜上, 傳送cookie的CORS的正確姿勢是:

  • 客戶端為fetch配置 credentials 項:credentials: `include`;
  • 服務端返回的responseHeader中攜帶如下兩個頭資訊欄位
    Access-Control-Allow-Origin: Origin;
    Access-Control-Allow-Credentials : true
    複製程式碼

The include part tells Chrome that you want to send a CORS request to the server which sends along the cookies properly

Chrome外掛裡怎麼做

通過了解CORS原理,我們知道了不支援CORS的服務端會返回一個正常的HTTP回應,只是由於沒有Aceess-control相關的響應頭,導致瀏覽器對響應進行了攔截。利用chrome.webRequest API,對onHeadersReceived進行監聽,即可在每次接收到 HTTP(S) 響應標頭時,新增需要的header欄位,讓瀏覽器以為這些頭資訊是服務端返回的,從而繞過CORS限制。

//Breaking the CORS Limitation
chrome.webRequest.onHeadersReceived.addListener(details=>window.onHeadersReceivedCallback(details), {
  urls: [`<all_urls>`]
}, ["blocking", "responseHeaders"]);
複製程式碼

簡單請求和複雜請求

只設定Access-Control-Allow-Origin和Access-Control-Allow-Crendentials兩個欄位,並不能滿足所有的場景。例如,我們在使用Antd的Upload元件進行上傳圖片時,Chrome會報如下錯誤:

undefined

Preflight

Access-Control-Allow-Origin響應頭欄位可以允許跨域 AJAX, 但對於非簡單請求,CORS 機制跨域會首先進行 preflight(一個 OPTIONS 請求,表示這個請求是用來詢問的)

undefined

簡單請求

具體是指請求方法是簡單方法且請求頭是簡單頭的 HTTP 請求。具體地,

  • 簡單方法包括GET, HEAD, POST。
  • 簡單頭包括:Accept, Accept-Language, Content-Language,以及值為application/x-www-form-urlencoded, multipart/form-data, text/plain 其中之一的 Content-Type 頭。
    對於非簡單請求瀏覽器會首先傳送 OPTIONS 請求(成為 preflight)

Access-Control-Request-Headers

Access-Control-Request-Headers 是 preflight 請求中用來標識真正請求將會包含哪些頭部欄位, 例如下述請求中Access-Control-Request-Headers欄位的值是x-requested-with,告知伺服器,實際請求將攜帶這個自定義請求首部欄位。伺服器據此決定,該實際請求是否被允許,如果允許,伺服器應當在對應的Access-Control-Allow-Headers響應頭中包含這個欄位。 否則即使返回 200 preflight 也會失敗。

undefined

檢視Antd Upload元件的原始碼,我們看到,在上傳圖片時,增加了x-requested-with請求頭,由此觸發了非簡單請求,會在CORS時先進行Option詢問

undefined

綜上,為了處理非簡單請求下的CORS,除了Access-Control-Allow-Origin和Access-Control-Allow-Crendentials外,我們還需要在Header劫持中新增如下配置

Access-Control-Allow-Methods:"*",
Access-Control-Allow-Headers:"Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With"
複製程式碼

相關文章