【譯】你應該瞭解的 CORS

joking_zhang發表於2019-07-20
原文:What you should know about CORS
作者:Nicolas Bailly
譯者:博軒

圖片描述

如果你和我一樣,第一次遇到 CORS (跨域資源共享),你想讓伺服器接收那些你拼接的 Ajax 請求並處理他們。所以你去 stackoverflow.com 複製一段程式碼來設定一些 HTTP Headers,讓請求可以正常工作。

但是,可能還有一些事情你應該知道。

CORS 是什麼,不是什麼

新手通常混淆的原因,就是因為他們並不明確 CORS 能做什麼。首先,CORS 並不是一種安全措施,實際上恰恰相反:CORS 是一種繞過“同源策略(SOP)”的方法。同源策略是一種安全措施,阻止您向其他域發出Ajax請求。

同源策略宣告一個域上的網站,無法向另一個域發出 XMLHttpRequest(XHR) 請求。這可以防止惡意網站向已知網站(比如 Facebook 或者 Google)發出請求,改變使用者的登入狀態,以便可以冒充其他使用者。此策略由瀏覽器實現(所有瀏覽器都實現了同源策略,儘管實現細節上存在細微的差別),這意味著此策略並不適用於從伺服器,或者任何其他HTTP客戶端(比如 cURLpostman)發出的請求。此外,伺服器同樣無法完全控制它:伺服器將處理每個請求,並假設他們都來自可信域,請求是否會被阻止完全取決於瀏覽器。

同源策略絕不意味著防止攻擊者向您的伺服器發出請求(因為攻擊者顯然不會使用瀏覽器)。它只是為了防止合法使用者在使用知名瀏覽器瀏覽網站時,在不知情的情況下,向你的網站發起請求。

CORS 是一種繞過同源策略的方法,在某些情況下,您希望一些特殊的站點可以向你的伺服器發起請求,即使正常情況下它會被阻止。(通常,是允許您的前端應用向您的API發出請求)。

CORS 是如何工作的

HTTP的相同,CORS基本上也是瀏覽器和伺服器之間的對話。假設你前端的域名為 domain-a.com ,後端API的域名為 domain-b.com,對話會是這樣的:

  • 瀏覽器:“Hey domain-b.comdomain-a.com上的這個指令碼要向你發起一次Ajax請求,但是我應該阻止它,除非你告訴我這個請求是沒問題的。”
  • 伺服器:“我不知道,但是我可以告訴你,https://domain-a.com 只允許傳送 GET,POST,OPTIONS 和 DELETE 請求,並且需要每10分鐘驗證一次。”
瀏覽器想了想:“ yeah,這是個正確的域名,我應該給他傳送請求。”
  • 瀏覽器:“Hey domain-b.com,我想在這個終端向你傳送 POST 請求。”
  • 伺服器:“沒問題,這是你的 200

或者,如果使用者位於不同的域,則對話會更短:

  • 瀏覽器:“Hey domain-b.commalicious-domain.com(惡意站點)上的這個指令碼要向你發起一次Ajax請求,但是我應該阻止它,除非你告訴我這個請求是沒問題的。”
  • 伺服器:“我不知道,但是我可以告訴你,https://domain-a.com 只允許傳送 GET,POST,OPTIONS 和 DELETE 請求,並且需要每10分鐘驗證一次。”
瀏覽器想了想:“ Oh,這不是正確的域名,我們最好不要傳送請求”,然後在控制檯列印了錯誤。
譯註:第二種,使用開發者工具檢視時,看不到 Response Headers,但是可以看到下圖中的提示:

圖片描述

Node CORS 測試地址

在瀏覽器中的樣子

在上面的小場景中,瀏覽器提出的第一個問題稱為 預檢請求,對應的 HTTP 謂詞是 OPTIONS。遇到這種預檢請求,伺服器應該總是返回一個 200 的響應,沒有正文,但是會包含 Access-Control-Allow-Origin ,以及一些其他響應頭。在我們的示例中響應頭如下:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://domain-a.com
Access-Control-Allow-Methods: GET, POST, OPTIONS, DELETE
Access-Control-Max-Age: 3600複製程式碼

它告訴瀏覽器,它只能響應來自 domain-a.com 的請求,可以處理 GET, POST, OPTIONS 或者 DELETE 請求(PUT 請求會被阻止),並且他可以快取此資訊 3600 秒,所以它不需要都發起一個新的 OPTIONS 請求。

圖片描述

當然,如果我們使用其他域名,這將不起作用。瀏覽器會傳送 OPTIONS 請求,然後在控制檯中丟擲異常,並且永遠不會傳送 POST 請求。

圖片描述

很直接,對吧?

但是,也存在一些陷阱...

關於 CORS 的棘手問題

所求請求都包含 CORS 頭(headers)

您可能會認為,如果您的伺服器響應 OPTIONS 請求時返回 200,然後你將這些正確的響應頭去掉。然後你將看到瀏覽器先傳送了 OPTIONS 請求,然後傳送了其他請求,其他請求掛掉了... 這是因為每個請求(GET, POST, 或者其他請求)都應該包含相同的響應頭:“Access-Control-Allow-Headers”。

並非所有請求都會觸發預檢請求

有一些請求不會觸發預檢請求,比如 GET 請求,或者 Content-Type 設定為 application/x-www-form-urlencoded 的 POST 請求。這些是瀏覽器一直允許的“簡單請求”,(即使在CORS不支援的情況下,你依然可以使用超連結(a標籤)或者使用 POST 請求向其他網站提供表單,您可以在此處找到完整列表。在 POST 請求的情況下,結果會有些違反直覺:瀏覽器將發出 POST 請求(因此您的伺服器可能會保留一些資料),然後忽略響應。

在傳統的Web應用程式中,您可以使用 application/json 作為 content-type,因此會有預檢請求,但請記住,您的伺服器可能仍會收到來自其他域的 POST 請求,因此請不要盲目接受它們。

被允許的域名必須包含協議

您不能只將 mydomain.com 當做域名使用,它還需要包含協議,(例如:https://mydomain.com)。有趣的是,你不能同時接收 httphttps ,因為......

您只能允許一個域

您可以使用 Access-Control-Allow-Origin: * 來允許每個域訪問,也可以只允許一個域訪問。這意味著如果您需要多個域來訪問您的API時,您需要自己處理它。

處理此問題的最簡單方法是在伺服器上維護允許訪問的域列表,如果域位於該列表中,則動態的改變響應頭的內容。下面是一個 PHP 的例子:

$allowedDomains = [
    "http://www.mydomain.com",
    "https://www.mydomain.com",
    "http://www.myotherdomain.com",
    "http://www.myotherdomain.com",
];

$originDomain = $_SERVER['HTTP_ORIGIN'];

if (in_array($originDomain, $allowedDomains)) {
    header("Access-Control-Allow-Origin: $originDomain");
};複製程式碼

或者 Node.js 的例子(改編自這個 stackoverflow 答案

app.use(function(req, res, next) {
  const allowedOrigins = [
    "http://www.mydomain.com",
    "https://www.mydomain.com",
    "http://www.myotherdomain.com",
    "http://www.myotherdomain.com",
  ];
  const origin = req.headers.origin;
  if(allowedOrigins.indexOf(origin) > -1){
    res.setHeader('Access-Control-Allow-Origin', origin);
  }
  return next();
});複製程式碼
同源策略適用於 Chrome 和 Safari 的檔案系統,不適用於 Firefox 的

如果您向本地檔案發出請求,Firefox會認為它始終位於同一個域上並允許該請求。基於 Webkit 的瀏覽器(如Chrome或Safari)會將此視為安全風險,並阻止對本地檔案的Ajax查詢。解決這個問題的唯一方法是使用Firefox,或安裝將傳送 Access-Control-Allow-Origin: * 響應頭的Web伺服器。
正如 @brianjenkins94 在評論中指出的那樣,您也可以用 --disable-web-security 引數來啟動Chrome 。

iOS WKWebview需要CORS

如果您正在開發使用 webview(使用Cordova或Ionic)的移動應用程式,Android將不會給您帶來任何麻煩,但iOS上的新 WKWebview 將需要CORS。這意味著您幾乎必須始終將 Access-Control-Allow-Origin 標頭設定為 * ,但實際上這並不理想。
另一個選擇是不在您的應用程式中發出Ajax請求並使用 cordova 外掛來生成本機 http 請求,這將很方便的繞過同源策略。

謝謝閱讀 !
如果您想要更深入地瞭解CORS,請訪問MDN:
developer.mozilla.org...

本文已經聯絡原文作者,並授權翻譯,轉載請保留原文連結


相關文章