Once upon a time

CORS 為 Cross-Origin Resource Sharing 縮寫,意指「跨來源資源共用」。

從前從前,有個人叫做小明,他正尋思寫一個自己的網站,網址為 http://smallming.me,有一天,他得知他的好友小華也寫了個網站,網址為 http://smallhua.me;這小明看到小華的網站中,有很多很炫炮的圖片,以及生動的 js 特效,貪圖方便的小明,就想著把這些資源放到自己網站上,加快自己寫網站的速度,這些資源舉例如下:

http://smallhua.me/無所不能.js
http://smallhua.me/氣勢滂礡背景圖.jpg
http://smallhua.me/api/news

當小明把小華原始碼中的網址「搬進」自己網站原始碼之後,小明一瀏覽自己頁面,發現 devtools console 噴了一大堆紅字寫著:

Failed to load http://smallhua.me/無所不能.js: No 'Access-Control-Allow-Origin' header is present on the requested resource...
Failed to load http://smallhua.me/氣勢滂礡背景圖.jpg: No 'Access-Control-Allow-Origin' header is present on the requested resource...
Access to XMLHttpRequest at 'http://smallhua.me/api/news' from origin 'http://smallming.me' has been blocked by CORS policy...

# 特別說明,上述訊息為示意文字,非真正撰寫實際案例獲得的正確例外訊息。

為什麼會這樣呢?先看一段 CORS 的說明:

CORS 是一種使用額外 HTTP 標頭令目前瀏覽網站的使用者代理 (en-US) 取得存取其他來源(網域)伺服器特定資源權限的機制。當使用者代理請求一個不是目前文件來源——例如來自於不同網域(domain)、通訊協定(protocol)或通訊埠(port)的資源時,會建立一個跨來源 HTTP 請求(cross-origin HTTP request)。

基於安全性考量,程式碼所發出的跨來源 HTTP 請求會受到限制。例如,XMLHttpRequest 及 Fetch 都遵守同源政策(same-origin policy)。這代表網路應用程式所使用的 API 除非使用 CORS 標頭,否則只能請求與應用程式相同網域的 HTTP 資源。

很難一下子就看懂在講什麼,但其實意思就是:


「你個小混蛋,給我聽好了,不是你們家的東西(資源/API),你必須得獲得對方允許才能用。」

Same-Origin Policy, 同源政策

這時就要先講一下瀏覽器的「Same-Origin Policy, 同源政策」了;同源政策為「當你請求的伺服器網址跟你的網址不是同一個」,即為不同源,例如:

http://smallming.me vs http://smallhua.me 網址不同
http://www.smallming.me vs http://smallhua.me 網址不同
http://smallming.me vs https://smallming.me 協定不同
http://smallming.me vs http://smallming.me:81 port 不同

不同源你是 call 不了第三方要資料的,所以為了安全,瀏覽器會強制讓所有請求遵守 CORS,簡單說就是「你請求,我允許/不允許」的規則。那怎麼樣才能跨域 call 別人要資料呢,答案就是「滿足 CORS 定義之下,發出請求,並且讓對方伺服器去定義允許誰/不允許誰,開放讓指定的不同源網址來要資料」。

這裡有篇 文章 提到的同源政策,還區分為:DOM 同源政策,以及 Cookies 同源政策。

CORS 請求類型

在 CORS 定義中,有兩種請求方式:

  • 簡單跨域請求
  • 非簡單跨域請求

這邊就不講太多細節 (請 參考 這),區別就是,簡單請求會直接把請求送到對方伺服器去要資料,簡單請求,則會先發一個「叩叩叩,May I?」(preflight request, 預檢請求) 去問對方,要回「我允許的 HTTP 方法與表頭」給請求方,讓他知道需不需要發送真正的請求要資料,當對方給你的允許方法與表頭名單,為預檢請求之後,真正要發出請求的方法與表頭,就表示瀏覽器通過 CORS,也就是對方回應你的預檢請求說:「你可以來要資料了」。

CORS 怎麼運作的

當發出請求的一方,向不同源伺服器要資料,瀏覽器會在這次請求的 Header 加上請求方的 Origin (小明請求小華時,小明的 request 中會包含 Origin 表頭:http://smallming.me) 一併送到對方伺服器,此值包含了

{http or https 通訊協定} + 域名 + port (80 通常省略),例如 http://smallming.me

小華接收到小明的請求後,根據小華自己的實作,去判斷這是不是他允許的請求方 (判斷 Origin 有沒有在自己的允許名單內),若允許,就回給小明說:「OK, 給你資料吧」或是「OK, 來跟我要資料吧 (回給預檢請求)」。

詳細的請求回應運作過程、過程中使用到的表頭,以及其他不在一般情況的請求/回應場景,需另外實作不同的邏輯來實現 CORS,來保證其跨域資源共用的溝通方式是安全的。

CORS Cookies

「哈囉,我可不可以要你的 Cookies 用一下?我要的 Cookies 名字是 bala。」

若對方想要允許 (回 可以啊可以啊),則可以在回應表頭加上:

Access-Control-Allow-Origin: http://smallming.me
Access-Control-Allow-Credentials: true

另外需注意,要請求使用對方的 Cookies 時,對方實作加入允許名單的表頭中,Access-Control-Allow-Origin 不能是 *,必須指定一個完整網址。

最後

因為想用白話一點的描述,嘗試讓自己看懂什麼是 CORS,所以官方文件、網路文章內容中,提過的/實作細節就盡量不重複在寫了。有任何問題或想法,歡迎留言交流,如果寫的內容有錯誤的地方,希望能不吝指點,感謝。

另外,筆者一併整理了討論到 CSRF 時,可一併吸收研讀的主題

參考連結



文章作者: littlebookboy
永久鏈結: https://blog.genesu.me/2021/06/learn-about-cors/
許可協議: 署名-非商業性使用-相同方式共享 4.0 國際(CC BY-NC-SA 4.0)