原文: What you should know about CORS
做者:Nicolas Bailly
譯者:博軒
若是你和我同樣,第一次遇到 CORS (跨域資源共享),你想讓服務器接收那些你拼接的 Ajax
請求並處理他們。因此你去 stackoverflow.com 複製一段代碼來設置一些 HTTP Headers,讓請求能夠正常工做。php
可是,可能還有一些事情你應該知道。前端
新手一般混淆的緣由,就是由於他們並不明確 CORS 能作什麼。首先,CORS 並非一種安全措施,實際上偏偏相反:CORS 是一種繞過「同源策略(SOP)」的方法。同源策略是一種安全措施,阻止您向其餘域發出Ajax
請求。node
同源策略聲明一個域上的網站,沒法向另外一個域發出 XMLHttpRequest(XHR) 請求。這能夠防止惡意網站向已知網站(好比 Facebook 或者 Google)發出請求,改變用戶的登陸狀態,以即可以冒充其餘用戶。此策略由瀏覽器實現(全部瀏覽器都實現了同源策略,儘管實現細節上存在細微的差異),這意味着此策略並不適用於從服務器,或者任何其餘HTTP客戶端(好比 cURL ,postman)發出的請求。此外,服務器一樣沒法徹底控制它:服務器將處理每一個請求,並假設他們都來自可信域,請求是否會被阻止徹底取決於瀏覽器。web
同源策略毫不意味着防止攻擊者向您的服務器發出請求(由於攻擊者顯然不會使用瀏覽器)。它只是爲了防止合法用戶在使用知名瀏覽器瀏覽網站時,在不知情的狀況下,向你的網站發起請求。json
CORS 是一種繞過同源策略的方法,在某些狀況下,您但願一些特殊的站點能夠向你的服務器發起請求,即便正常狀況下它會被阻止。(一般,是容許您的前端應用向您的API發出請求)。後端
與HTTP的相同,CORS基本上也是瀏覽器和服務器之間的對話。假設你前端的域名爲 domain-a.com
,後端API的域名爲 domain-b.com
,對話會是這樣的:跨域
domain-b.com
,domain-a.com
上的這個腳本要向你發起一次Ajax
請求,可是我應該阻止它,除非你告訴我這個請求是沒問題的。」https://domain-a.com
只容許發送 GET,POST,OPTIONS 和 DELETE 請求,而且須要每10分鐘驗證一次。」瀏覽器想了想:「 yeah,這是個正確的域名,我應該給他發送請求。」
domain-b.com
,我想在這個終端向你發送 POST 請求。」200
」或者,若是用戶位於不一樣的域,則對話會更短:瀏覽器
domain-b.com
,malicious-domain.com
(惡意站點)上的這個腳本要向你發起一次Ajax
請求,可是我應該阻止它,除非你告訴我這個請求是沒問題的。」https://domain-a.com
只容許發送 GET,POST,OPTIONS 和 DELETE 請求,而且須要每10分鐘驗證一次。」瀏覽器想了想:「 Oh,這不是正確的域名,咱們最好不要發送請求」,而後在控制檯打印了錯誤。
譯註:第二種,使用開發者工具查看時,看不到 Response Headers,可是能夠看到下圖中的提示:
在上面的小場景中,瀏覽器提出的第一個問題稱爲 預檢請求,對應的 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
請求。
很直接,對吧?
可是,也存在一些陷阱...
您可能會認爲,若是您的服務器響應 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
)。有趣的是,你不能同時接收 http
和 https
,由於......
您可使用 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();
});複製代碼
若是您向本地文件發出請求,Firefox會認爲它始終位於同一個域上並容許該請求。基於 Webkit 的瀏覽器(如Chrome或Safari)會將此視爲安全風險,並阻止對本地文件的Ajax
查詢。解決這個問題的惟一方法是使用Firefox,或安裝將發送 Access-Control-Allow-Origin: *
響應頭的Web服務器。
正如 @brianjenkins94 在評論中指出的那樣,您也能夠用 --disable-web-security
參數來啓動Chrome 。
若是您正在開發使用 webview
(使用Cordova或Ionic)的移動應用程序,Android將不會給您帶來任何麻煩,但iOS上的新 WKWebview
將須要CORS。這意味着您幾乎必須始終將 Access-Control-Allow-Origin
標頭設置爲 *
,但實際上這並不理想。
另外一個選擇是不在您的應用程序中發出Ajax
請求並使用 cordova
插件來生成本機 http
請求,這將很方便的繞過同源策略。
謝謝閱讀 !
若是您想要更深刻地瞭解CORS,請訪問MDN:
developer.mozilla.org...
本文已經聯繫原文做者,並受權翻譯,轉載請保留原文連接