[譯] 理解 CORS

原文: https://medium.com/@baphemot/understanding-cors-18ad6b478e2b前端

"OK, but no"

只要用過 AJAX,你應該就很熟悉瀏覽器控制檯中出現的以下報錯: node

Failed to load https://example.com/: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘https://anfo.pl' is therefore not allowed access. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

當你看到這個信息,就意味着響應失敗了;但你依然能在瀏覽器開發工具的網絡 tab 裏看到返回數據 -- 這是什麼狀況呢?webpack

跨域資源共享(CORS: Cross-Origin Resource Sharing)

你所觀察到的這種行爲是瀏覽器 CORS 實現機制的效果。在 CORS 成爲標準以前,因爲安全緣由,沒有辦法跨域調用 API。也就是(必定程度上依舊是)被所謂同源策略(Same-Origin Policy)限制住了。git

CORS 機制是爲了在承認用戶發起的請求的同時,阻止那些惡意 JS;並在如下狀況發起的 HTTP 請求時被觸發:github

  • 一個不一樣的域(好比從 example.com 的站點調用 api.com)
  • 一個不一樣的子域(好比從 example.com 的站點調用 api.example.com)
  • 一個不一樣的端口(好比從 example.com 的站點調用 example.com:3001)
  • 一個不一樣的協議(好比從 https://example.com 的站點調用 http://example.com)

這種機制阻止了當你已經登陸 www.yourbank.com 的狀況下,攻擊者在各類網站上植入的腳本(好比經過 Google Ads 顯示的廣告)向 www.yourbank.com 發起的攜帶 你的身份憑證 的 AJAX 請求。web

對於「簡單的」 GET 或 POST 請求,若是服務器沒有對其做出攜帶特殊 HTTP 頭部的響應 -- 請求依然被髮送而且數據也照樣被返回,但瀏覽器將不容許 Javascript 訪問該響應。chrome

若是瀏覽器嘗試着去弄一個「沒那麼簡單」的請求(好比一個包含了 cookie 的請求,或 Content-type 不是 application/x-www-form-urlencodedmultipart/form-datatext-plain 三者之一的),則被稱爲預檢(preflight)的機制將被用到,而且一個 OPTIONS 請求會被髮往服務器。express

關於「沒那麼簡單」的請求,一個常見的例子是在請求中加入 cookie 或自定義頭部 -- 若是瀏覽器發送了這樣的請求且服務器沒有正確響應的話,則只有預檢調用會發送(不包含額外的頭部),而瀏覽器本應使用的真實的 HTTP 請求就不會被髮送了。npm

那些 Access-Control-Allow-什麼什麼的...

在 CORS 請求和響應中,都用到了一些 HTTP 頭部,其中如下這幾個是你必須理解的:後端

Origin

該頭部是客戶端發起的請求的一部分,包含了應用所在的域。因爲安全緣由,瀏覽器不會容許用戶重寫這個值。

Access-Control-Allow-Credentials

該頭部只須要在服務器支持經過 cookie 認證的狀況下出如今響應中。這種狀況下,其惟一合法值就是 true。

Access-Control-Allow-Methods

一個逗號分隔的、表示服務器將會支持的 HTTP 請求動詞(如 GET, POST)列表。

Access-Control-Allow-Headers

格式爲一個逗號分隔的列表,表示服務器將會支持的請求頭部值。若是使用了自定義頭部(好比 x-authentication-token),則應該將其置於這個 ACA 頭部(譯註:即 Access-Control-Allow-Headers)響應中,並返回到 OPTIONS 調用中;除非該請求被阻塞了。

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
複製代碼

Access-Control-Expose-Headers

類似的是,該響應應包含一個頭部列表,表示將在實際的響應中出現的值,並應在客戶端中有效。全部其餘頭部則會被限制。

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
複製代碼

如何搞定 CORS 「錯誤」 ?

首先要清楚的是,CORS 行爲並不是一種錯誤 -- 這種機制致力於保護你的用戶、你自己,或你調用的站點。

有時,缺乏合適的頭部,會致使客戶端的錯誤執行(如丟失了 API key 等認證信息)。

取決於你面臨的場景,如下手段能夠「搞定這種錯誤」:

A -- 我開發前端,也能控制後端,或者認識那個開發後端的哥們

這是最好的狀況了 -- 你能根據調用,在服務器上實現合適的 CORS 響應。若是 API 用 node 的 express 實現,那麼簡單的使用 cors 包(譯註:https://github.com/expressjs/cors)就能夠了。若是要保證站點的適度安全,能夠考慮爲 Access-Control-Allow-Origin 設置一個白名單。

B -- 我開發前端,且暫時控制不了後端,我須要一個臨時的辦法

這是次優的狀況,由於其實這就是手段 A,只是暫時性的受限。爲了臨時解決,可讓瀏覽器忽略 CORS 機制 -- 好比使用 ACAO Chrome 擴展(譯註: 或指 Allow-Control-Allow-Origin: * 擴展) 或用以下參數在啓動 Chrome 時徹底禁止 CORS:

chrome --disable-web-security --user-data-dir
複製代碼

切記,這將禁止瀏覽器會話期間 全部 網站的 CORS 機制;要當心慎用。

另外的替代方法是使用 devServer.proxy(假設你用到了 webpack 作開發);或使用一個 CORS-as-a-service 解決方案,好比 https://cors-anywhere.herokuapp.com/ 。

C -- 我開發前端,並老是控制不了後端的

Ok,如今事兒大了。首先要搞清爲何服務器沒有發送適當的頭部。

也許是不容許第三方應用訪問其 API ?又或者其 API 只服務於服務器端而非瀏覽器?要麼就是你須要在 URL 中發送認證令牌?

若是你依然認爲能夠經過瀏覽器訪問數據,就得在瀏覽器應用和 API 之間編寫本身的代理了,就相似於咱們在手段 B 中作的那樣。

在中間加一個代理

該代理沒必要和你的應用運行在一樣的域下,只要當代理自己和客戶端通信時正確支持 CORS 就行。代理和 API 之間的通信就徹底沒必要支持 CORS 了。

你既能夠編寫本身的平臺,也可使用諸如 https://www.npmjs.com/package/cors-anywhere 的成熟方案。

要記住若是你須要支持身份憑證,這樣的辦法會引入一個安全風險。

關於 CORS 的更多

若是但願學習更多關於 CORS 的細節,推薦閱覽這篇 MDN 文章 (https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)。


(end)

----------------------------------------

長按二維碼或搜索 fewelife 關注咱們哦

相關文章
相關標籤/搜索