翻譯:瘋狂的技術宅
本文首發微信公衆號:jingchengyideng
歡迎關注,天天都給你推送新鮮的前端技術文章javascript
CORS 與 cookie 在前端是個很是重要的問題,不過在大多數狀況下,由於先後端的 domain 通常是相同的,因此不多去關心這些問題。或者只是要求後端設置 Access-Control-Allow-Origin: *
就好了,不多去了解背後運做的機制。前端
針對這個問題,MDN 上有很是詳細的解釋,因此這篇文章主要在於整理重點和實際操做時常常出現的問題。java
爲了防止 javascript 在網頁上隨意撒野,同源策略規定了某些特定的資源,代碼必須在同源的狀況下才能夠存取。git
什麼是同源呢?一份 document
的來源,由 protocol、host 和 port 來定義。也就是說若是文件1來自http://kalan.com
,而文件2來自於 https://kalan.com
他們就不算是同源。那若是是子域名呢?像是 https://api.foobar.com
和 https://app.foobar.com
。由於他們的 host 不一樣,因此也不算同一個源。程序員
而有些資源是原本就可以經過跨來源取得的:github
<img />
<video />
, <audio />
<iframe />
:能夠經過定義 header 來防止他人嵌入<link rel="stylesheet" href />
載入的CSS腳本<script src="" />
載入的 Javascript經過代碼發出的跨源請求則會受到同源策略的限制(如Fetch,XHR)。面試
很顯然,這樣的規定太過嚴格了。若是都要限制在同源策略下的話,先後端開發會難以進行,也沒辦法用 XHR 的方式套用其餘 SDK 的 API。也所以出現了 CORS( Cross-Origin Resource Sharing)的機制。json
不少人都以爲 CORS 是前端才須要具有的知識。不過 CORS 一般須要後端設定相關的 HTTP 頭,而且瞭解背後的含義纔有辦法正確運做。segmentfault
那麼跨來源請求是怎麼運做的呢?後端
主要是由兩個 Header 來作相對的存取控制:請求當中的 Origin
和響應中的 Access-Control-Allow-Origin
。
只要發送請求時的 Origin 和響應頭中 Access-Control-Allow-Origin
的值相同,或是 Access-Control-Allow-Origin: *
(表明容許任何域存取資源),此時就會放寬 CORS 的限制,容許存取跨域資源。
若是不符合 CORS 策略的話,會顯示下列信息:
若是你嘗試去讀取回傳的物件,還會獲得警告。
首先,若是咱們按照提示中所說的,將 fetch mode 改爲 no-cors
會發生什麼事呢?的確,咱們把煩人的錯誤信息給處理掉了,可是狀況彷佛並無變好。
no-cors
並非靈丹妙藥,就算用了這個模式,CORS 也不會所以就打開大門,也就是你的請求並不會成功發出。也所以出現了 SyntaxError: Unexpected end of input
這個錯誤。這個模式一般是跟Service Worker搭配使用的。
從上面這個實驗當中可知,要解除CORS的封印只有一招,就是在服務器端加上正確的 Control-Access-Allow-Origin
(host 必須跟原來相同或是*
)。
另外,CORS 這個機制只會運做在 javascript 送出 XHR 或 fetch 時,通常 curl 或 postman 並無這個機制,因此也所以經常在測試 API 端點時會忽略這件事,致使先後端在測試 API 時發生出入。
有些跨來源請求不會發生 preflight,而有些請求則會,MDN上寫的清清楚楚:
Content-Type
(注意是請求頭,不是響應頭),則必須是下列的值:application/x-www-form-encoded
,text/plain
,multipart/form-data
也就是說若是不知足以上條件的話,就會發出 preflight 請求。
咱們試着把 Content-Type
改成 application/json
來測試一下(不能爲 application/x-www-form-encoded
,text/plain
,multipart/form-data
)。
所謂的 preflight 就是請求會先用 HTTP 的 OPTION 方法去另一個域敲門,確認沒問題後纔會送出真正的請求。一旦觸發了這個條件,事情就會變得麻煩得多。
Access-Control-Allow-Headers
,且必須包含全部不在條件內 header,不然沒法經過。若是沒有經過 preflight check 的話,會獲得錯誤信息以下:
Access to fetch at 'http://localhost:3001/trigger-preflight' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
或是你沒有在 OPTIONS
的響應頭裏加上 Access-Control-Allow-Origin
:
Access to fetch at 'http://localhost:3001/trigger-preflight' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
若是成功的話,你會看到 network 裏有兩個請求,一個是 OPTIONS,另外一個則是真正的請求。
上圖爲 OPTION,下圖爲GET
若是咱們加上一個自制的頭呢?根據MDN所定義的條件,也應該觸發預檢請求才對,咱們加上一個X-Access-Token
看看會發生什麼事。
的確沒法經過preflight,若是要經過的話,必須再把 X-Access-Token
加入 Access-Control-Allow-Headers
中。
cookie 並不能跨域傳遞,也就是說不一樣 origin 來的 cookie 沒辦法互相傳遞及存取,否則就天下大亂了。
不過若是你在 a 域送出了 b 域的請求,且 b 域回傳了 cookie 的信息,那麼在 a 域會以 b 域的形式儲存一份cookie,若是沒有設定 withCredentials
或是 credentials: ‘include’
的話,就算服務器回傳了 Set-Cookie
,同樣不會被寫入。以下圖:
服務器回傳Set-Cookie
沒有寫入瀏覽器中
在通常狀況下若是再使用 b 域的 API,cookie 是不會自動被送出去的。這個狀況下,你必須在 XHR
設定 withCredentials
或是 fetch
的選項中設置 { credentials: 'include' }
,由於這也是一個跨域請求,因此也必須按照 CORS 條件加入 Access-Control-Allow-Origin
爲了不安全性的問題,瀏覽器還有規定 Access-Control-Allow-Origin
不能爲*
。
Access to fetch at 'http://localhost:3001/cookie' from origin 'http://localhost:3000' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
不過僅僅這樣仍是不夠,瀏覽器會自動拒絕沒有 Access-Control-Allow-Credentials
的響應,因此若是要將身份信息傳到跨域的服務器中,必須額外加上 Access-Control-Allow-Credentials: true
。若是這些都設定成功,應該會像下圖這樣,在 Request Cookie能夠看到 cookie 被成功送出。
Request Cookies 裏有個 jack!
好吧,若是你成功設定了這些東西,但仍是有可能沒辦法把 cookie 送到服務器。那有可能會是如下幾種狀況:
可能使用者把你加入了黑名單,致使 cookie 沒法成功送出
解決方法:
在Safari 中有時會開啓「阻止全部Cookie」這一選項,這在調試時會讓你嚐到很多苦頭。
要處理 CORS 是件吃力不討好的事情,尤爲是有時在跑 CI/CD以前忘記加上 Access-Control-Allow-Origin
或是 Access-Control-Allow-Credentials
,那麼部署可能又是一天之後的事了。此次把一些常見的問題整理起來,但願之後若是再有相似的情形能夠知道怎麼處理。
最後附上源代碼。