【譯】你應該瞭解的 CORS

原文: What you should know about CORS
做者:Nicolas Bailly
譯者:博軒

圖片描述

若是你和我同樣,第一次遇到 CORS (跨域資源共享),你想讓服務器接收那些你拼接的 Ajax 請求並處理他們。因此你去 stackoverflow.com 複製一段代碼來設置一些 HTTP Headers,讓請求能夠正常工做。php

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

CORS 是什麼,不是什麼

新手一般混淆的緣由,就是由於他們並不明確 CORS 能作什麼。首先,CORS 並非一種安全措施,實際上偏偏相反:CORS 是一種繞過「同源策略(SOP)」的方法。同源策略是一種安全措施,阻止您向其餘域發出Ajax請求。node

同源策略聲明一個域上的網站,沒法向另外一個域發出 XMLHttpRequest(XHR) 請求。這能夠防止惡意網站向已知網站(好比 Facebook 或者 Google)發出請求,改變用戶的登陸狀態,以即可以冒充其餘用戶。此策略由瀏覽器實現(全部瀏覽器都實現了同源策略,儘管實現細節上存在細微的差異),這意味着此策略並不適用於從服務器,或者任何其餘HTTP客戶端(好比 cURLpostman)發出的請求。此外,服務器一樣沒法徹底控制它:服務器將處理每一個請求,並假設他們都來自可信域,請求是否會被阻止徹底取決於瀏覽器。web

同源策略毫不意味着防止攻擊者向您的服務器發出請求(由於攻擊者顯然不會使用瀏覽器)。它只是爲了防止合法用戶在使用知名瀏覽器瀏覽網站時,在不知情的狀況下,向你的網站發起請求。json

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...

本文已經聯繫原文做者,並受權翻譯,轉載請保留原文連接
相關文章
相關標籤/搜索