credit:https://medium.com/@baphemot
譯自:https://medium.com/@baphemot/understanding-cors-18ad6b478e2b
“呃。。還行, 但不夠”
如果你經常跟AJAX call打交道,那麼你肯定遇到過下面這個錯誤。
如果你看到這條訊息,意味著響應失敗了,但你還是能在Console裡的Network標籤下,看到返回的資料。
那麼,這裡到底是怎麼一回事呢?
跨源資源共享(CORS)
你所遇到的這種行為就是瀏覽器跨域的實現。
考慮到安全問題,在跨域標準化之前,如果你想呼叫一個節點在不同域的API, 是不存在的。這種呼叫,會因為 Same-Origin 政策被阻止。
設計CORS這種機制是為了,第一,使你所發出的請求能代表你自身; 第二, 阻止那些流氓JS發出的請求; 第三,這種機制會被啟用,無論何時你傳送請求到:
1). 不同的域名。(eg. 應用在 example.com 呼叫 api.com)
2). 不同的子域名。(eg. 應用在 example.com 呼叫 api.com)
3). 不同的埠。(eg. 應用在 example.com 呼叫 example.com:3001)
4). 不同的協議。(eg. 應用在 example.com 呼叫 example.com)
通過這種機制,我們能夠阻止黑客的指令碼攻擊,以防當你登陸,比如銀行網站,的時候替換你的驗證資訊。
如果你的瀏覽器嘗試發起一個‘不簡單’的請求(比如: 一個請求包含了cookies, 或者 Content-type
不是application/x-ww-form-urlencoded
, multipart/form-data
或者 text-plain
)這時候會呼叫有一種叫做 預檢(preflight)的機制,然後會傳送一個options
請求到伺服器。如果伺服器的響應,沒有攜帶特定的headers, 隨後的‘簡單‘get
或post
請求還是會傳送,但是瀏覽器不會允許JS去訪問的收到的資料。
如果你明確想要新增cookies,自定義headers和其他一些features,那這個請求就不再是一個‘簡單’請求。那麼伺服器就不會合理地響應,請求也不會被髮送。
Access-Control-Allow-What?
CORS使用很少的HTTP請求頭(在請求和響應裡都是),但是有一點你必須明白,而且有能力去在工作中應用:
Access-Control-Allow-Origin
這個請求頭一般會被伺服器端返回,它的值代表了哪些域名你有權可以訪問。 它的值可以為:
*
允許訪問任何域- 一個安全驗證過的域名(eg. example.com)
如果你請求客戶端傳一些用作驗證的請求頭,比如cookies, 那麼你就不能將Access-Control-Allow-Origin的值設定為*
—必須是安全驗證過的域名才可以!
Access-Control-Allow-Credentials
如果一個伺服器支援通過cookies來驗證,那麼必須要在響應裡帶上這個請求頭。
True是其唯一有有效的值。
Access-Control-Allow-Headers
一個逗號分隔的list,存放代表伺服器願意支援的請求頭。(eg. 比如x-authentication-token
, 你需要將其包含在ACA header裡, 返回給前面提到的options
請求, 否則你的請求會被blocked)
Access-Control-Expose-Headers
跟上面相似,這個請求頭包含一系列使用者可用的headers,這些headers會出現在真實響應裡,而且客戶端是可以使用的。其他的所有header會被blocked。
Access-Control-Allow-Methods
這個比較簡單,存放所有服務端支援的HTTP請求型別(比如get
,post
)。
Origin
客戶端請求頭的一部分,其值包含客戶端app啟動處的域名。 出於安全考慮,瀏覽器將不允許你去重寫這個值。
如何消除‘CORS’錯誤
你不得不承認CORS並不是一種‘錯誤’。它是一種預期的機制為了去保護使用者,你,還有你傳送請求的目標網站。
有時候缺乏合理的請求頭是客戶端的一種錯誤的行為(eg. 缺少驗證資訊比如API key)。
這裡我將給你一些方法去“解決錯誤”,選擇哪種方法,這取決於你所應用的場景:
A - 我開發前端,後端我認識,聽我的 ;)
嗯這當然是最好的情況, 你就可以去實現合理的CORS響應在你所請求的伺服器端。如果一個API正在使用node的express
框架,你只要用一下cors
的包就行了。如果你想使你的網站更加合理安全,你可能要考慮使用一個白名單給Access-Control-Allow-Origin
請求頭。
B - 我開發前端,後端我不熟,暫時需要一個臨時的解決方案 :)
這是第二好的情況,因為這就是A情況,只不過有一些時間限制。如果你想臨時解決這個問題,你可以讓你的瀏覽器忽略CORS機制,舉個栗子,使用ACAO Chrome外掛,或者在用Chome的時候跑一下下面的flags:
chrome --disable-web-security --user-data-dir複製程式碼
重要:請記住這條命令會應用於所有網站,並且存在於整個瀏覽器會話中。請小心使用。
另一個路子就是,你可以使用devServer.proxy(假設你使用webpack去serve你的app)或者使用一個 CORS-as-a-service 解決方案,比如cors-anywhere.herokuapp.com/
C - 我開發前端,我想要調後端? 不存在的 :`(
好吧,現在事情就變得複雜了。首先,你應該可能需要搞清,為什麼伺服器端沒有傳送一個正確的請求頭。
可能它們不允許使用第三方的庫的app去訪問?可能它們的API只給伺服器端的應用使用, 而不是瀏覽器?可能你在請求時沒有傳送用於驗證的token?
如果你仍然認為你有能夠通過瀏覽器得到資料,你應該去寫一個自己的代理,存在於瀏覽器應用和API之間,就像我們在方案B中所做的一樣。
Adding a proxy in the middle
這個代理伺服器,不是必須和你的應用跑在相同的域上。只要使得這個代理伺服器,在與客戶端交流時支援CORS就可以。在與API交流時不是必須要支援CORS。
你可以寫一個自己的平臺,或者使用一個已有的解決方案,比如
記住,這種方法可能存在安全風險,如果你想要支援驗證的話。
更多關於 CORS
如果你想學更多關於CORS的細節,我推薦你去檢視更加細節化的MDN article.