1. 程式人生 > 實用技巧 >你應該瞭解的CORS

你應該瞭解的CORS

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

CORS 是什麼,不是什麼

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

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

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

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

CORS 是如何工作的

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

  • 瀏覽器:“Heynamedomain-b.comname,namedomain-a.comname上的這個指令碼要向你發起一次nameAjaxname請求,但是我應該阻止它,除非你告訴我這個請求是沒問題的。”
  • 伺服器:“我不知道,但是我可以告訴你,namehttps://domain-a.comname只允許傳送 GET,POST,OPTIONS 和 DELETE 請求,並且需要每10分鐘驗證一次。”

瀏覽器想了想:“ yeah,這是個正確的域名,我應該給他傳送請求。”

  • 瀏覽器:“Heynamedomain-b.comname,我想在這個終端向你傳送 POST 請求。”
  • 伺服器:“沒問題,這是你的name200name”

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

  • 瀏覽器:“Heynamedomain-b.comname,namemalicious-domain.comname(惡意站點)上的這個指令碼要向你發起一次nameAjaxname請求,但是我應該阻止它,除非你告訴我這個請求是沒問題的。”
  • 伺服器:“我不知道,但是我可以告訴你,namehttps://domain-a.comname只允許傳送 GET,POST,OPTIONS 和 DELETE 請求,並且需要每10分鐘驗證一次。”

瀏覽器想了想:“ Oh,這不是正確的域名,我們最好不要傳送請求”,然後在控制檯列印了錯誤。

譯註:第二種,使用開發者工具檢視時,看不到 Response Headers,但是可以看到下圖中的提示:

Node CORS 測試地址

在瀏覽器中的樣子

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

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

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

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

很直接,對吧?

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

關於 CORS 的棘手問題

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

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

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

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

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

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

您不能只將namemydomain.comname當做域名使用,它還需要包含協議,(例如:namehttps://mydomain.comname)。有趣的是,你不能同事接收namehttpname和namehttpsname,因為......

您只能允許一個域

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

處理此問題的最簡單方法是在伺服器上維護允許訪問的域列表,如果域位於該列表中,則動態的改變響應頭的內容。下面是一個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();
});

廣州品牌設計公司https://www.houdianzi.com

同源策略適用於 Chrome 和 Safari 的檔案系統,不適用於 Firefox 的

如果您向本地檔案發出請求,Firefox會認為它始終位於同一個域上並允許該請求。基於Webkit的瀏覽器(如Chrome或Safari)會將此視為安全風險,並阻止對本地檔案的nameAjaxname查詢。解決這個問題的唯一方法是使用Firefox,或安裝將傳送nameAccess-Control-Allow-Origin: *name請求頭的Web伺服器。

正如@brianjenkins94在評論中指出的那樣,您也可以用name--disable-web-securityname引數來啟動Chrome 。

iOS WKWebview需要CORS

如果您正在開發使用namewebviewname(使用Cordova或Ionic)的移動應用程式,Android將不會給您帶來任何麻煩,但iOS上的新nameWKWebviewname將需要CORS。這意味著您幾乎必須始終將nameAccess-Control-Allow-Originname標頭設定為name*name,但實際上這並不理想。

另一個選擇是不在您的應用程式中發出nameAjaxname請求並使用namecordovaname外掛來生成本機namehttpname請求,這將很方便的繞過同源策略。