CORS 是什麼? 為什麼要有 CORS?
2022年10月4日
相信很多人在開發時遇過,呼叫 API 但瀏覽器卻報 CORS 的相關錯誤;也因此 CORS 的問題經常會在面試中被問到。這篇文章會來討論為何會有這些錯誤 CORS? 以及要如何解決?
從同源政策開始談起
在了解為什麼會有 CORS 之前,我們需要先了解瀏覽器的同源政策 (same origin policy)。瀏覽器有一些安全策略,以確保資訊傳輸的安全性,同源政策即是其中一種安全相關的規範,它限制網頁只能存取同源的資源。
同源必須符合以下三者條件:
- 同通訊協定(protocol)
- 同網域(domain)
- 同通訊埠(port)
以 https://www.explainthis.io
來說,我們用同源的標準來看以下幾個例子,可以看出哪些屬於同源或非同源
1. https://www.explainthis.com // 不同網域(domain),非同源
2. http://www.explainthis.io // 不同通訊協定(protocol),非同源
3. http://www.explainthis.io:5000 // 不同通訊埠(port),非同源
4. https://www.explainthis.io/ilikejavascript // 同源
5. http://www.hello.explainthis.io // 非同源
所以當 https://www.explainthis.io
要存取非同源的資源來源時,例如: https://www.explainthis.com/blog/1
,這時就會出現瀏覽器的 CORS 報錯。如下圖:
這裡可以想一下,為什麼要有同源政策這個安全規範? 它保護了什麼
同源政策限制的是非同源的請求,那非同源請求會帶來什麼問題呢? 假設今天有一個使用者登入某一銀行網站 www.bank.com
,同時他剛好在使用另一個不安全的網站例如 www.stolemoney.com
,如果沒有同源政策的話,這個 stolemoney
網站可能可以輕易地存取這個使用者在 www.bank.com
裡的資料。
瀏覽器的同源政策就像是最基本的一層保護機制,讓不同源的網站無法存取到資源和資料。另外要注意的是,這個阻擋機制是在最後瀏覽器收到伺服器端回應後發生的; 也就是說,就算是非同源請求,如果伺服器端沒有做任何阻擋、並回傳結果,瀏覽器端其實是會成功收到回應,但因為違反同源政策,瀏覽器會攔截這個回應、並報錯。
解決非同源訪問 - 透過 CORS
在上一段落我們知道,瀏覽器的同源政策能提供一道保護網,但在實務開發上,我們幾乎不可避免去請求非同源的資源。在有同源政策的情況下,要何做到非同源請求? 沒錯,就是透過 CORS。
CORS 是指跨來源資源共用(Cross-Origin Resource Sharing)。要做到 CORS 會需要被請求端、伺服器端的配合。當要獲取的資源非同源時,瀏覽器會自動發起一個跨域 (CORS) 請求,如果請求的伺服器端有額外設定 HTTP 標頭 (Header) 來告訴瀏覽器是允許被該網域訪問,所以當面試官問「要如何解決 CORS 問題」時,最直接的回答就是「請後端工程師在伺服器端做 CORS 標頭的設定」,至於如何設定,讓我們繼續看下去。
簡單請求(Simple requests)
CORS 整個流程則又分為簡單請求和預檢請求,基本上請求都是預檢請求,只有當符合某些條件下的請求 (例如使用 GET
、HEAD
、POST
的方法) 時會是簡單請求(Simple requests)。符合簡單請求的條件還包括設定的標頭、Content-Type
的標頭值等,詳細的規範可以參考 MDN。
在符合簡單請求的情境下,瀏覽器會直接對伺服器端發送請求,並在 header 中的 origin 帶上來源
Origin: https://www.explainthis.io
接著,伺服器的回應回在 header 中的 Access-Control-Allow-Origin
加上允許的來源,或是使用星號來代表所有來源。這邊的 Access-Control-Allow-Origin
是關鍵,上一段提到,遇到 CORS 問題時,要請後端工程師設定,要做的設定就是 Access-Control-Allow-Origin
的設定,範例如下。
// 如果要允許所有跨域來源的請求,可以用星號
Access-Control-Allow-Origin:*
// 如果要允許特定來源的跨域請求,可以直接放入該來源
Access-Control-Allow-Origin: https://www.explainthis.io
預檢請求(Preflighted requests)
只要不符合簡單請求的條件,瀏覽器會先做一次 HTTP 請求,稱之為預檢請求(preflight),預檢請求的方法是 OPTIONS
,一旦預檢請求成功完成,真正的請求才會被送出。但是,預檢請求並不一定每次都會被觸發,伺服器在回應預檢請求時,可以在 Access-Control-Max-Age 標頭帶上預檢請求回應快取的秒數,也就是說,在這個秒數之內,預檢請求會被快取,不需要重新觸發,可以像簡單請求一樣,直接送出實際的請求。
在面試中也很常會被問到「為什麼需要預檢請求? 在正式求請前多一次請求,這樣不是很浪費資源嗎?」針對這個問題,我們可以先從安全性的考量回答,上一段有提到,同源政策只會擋回應,不會擋請求,所以假如某個惡意攻擊者發送 DELETE
的請求,同源政策不會擋下這個請求 (如果該請求後有回應,回應的部分才會擋下),換句話說如果沒有多一層過濾,惡意攻擊者任意發 DELETE
請求,就可能任意刪掉伺服器端的資源。有了預檢請求,等於是多一層過濾,當預檢請求通過了,才會對伺服器發送真正的請求。
安全性外,相容性也是預檢請求協助確認的一個點,因為網頁發展技術迭代的很快,很多新的技術在舊的網站並沒有支援,如果瀏覽器發送一個伺服器端沒支援的請求,可能導致伺服器端出問題。這時預檢請求會是一道防護,先確保伺服器端有支援,才真正發請求。