iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 27
8
Modern Web

你所不知道的各種前端 Debug 技巧系列 第 27

[Day 27] Cross-Origin Resource Sharing (CORS)

概覽

考量到安全問題,瀏覽器會以同源政策(Same-origin policy) 限制網頁對其他 Origin 的資源(Resource)存取,例如 AJAX、DOM、Cookie、圖片等等,然而透過 CORS 就能夠在滿足某些條件的情況下,突破同源政策限制取得其他 Origin 的資源。

網路上關於 CORS 的說明文章已經非常豐富,建議閱讀 MDN 的 Cross-Origin Resource Sharing (CORS)。本文將會對 CORS 進行重點整理,並加強說明文件中並未清楚解釋的部分,最後附上其他和 CORS 相關卻容易被忽略的觀念。

Cross-Origin 的定義

請參考 Same-origin policy,簡單來說,只要兩個網址的 Schema、Host、Port 皆相同就是 Same-origin,否則就是 Cross-origin。

取自 https://web.dev/same-site-same-origin/

 

啟動 CORS

CORS 分為簡單請求(Simple requests)和預檢請求(Preflighted requests)兩種,基本上能不能成功進行 CORS 都是看後端的造化,前端只能看著錯誤訊息請後端趕快修正。

Access to fetch at [url] from origin [origin] has been blocked by CORS policy: 理由

注意如果看到這段錯誤訊息代表 Request 已經正常送出並取得 Response,但因為違反 CORS policy,瀏覽器不讓 JavaScript 存取內容。

首要條件

發出 CORS Request 時瀏覽器會自動在 Request header 加上目前的 Origin(假設是 http://example.com),後端必須在 Response header 中加上相符的 Access-Control-Allow-Origin 才能完成 CORS:

Access-Control-Allow-Origin: *                   # 同意啦,哪次不同意
Access-Control-Allow-Origin: http://example.com  # 只允許 http://example.com

簡單請求

如果 Request method 是GETHEADPOST 其一,且 Request header 的 Content-Type 是以下其中一種就是簡單請求,後端不需再做額外設定。

  • application/x-www-form-urlencoded
  • multipart/form-data
  • text/plain

更詳細的規則請參考 Simple requests - MDN

預檢請求

只要不符合簡單請求的規則例如使用了 PUTDELETE 等 Method 或者 Content-Typeapplication/json,在送出該 Request 之前,瀏覽器會先進行一次預檢(Preflight),和簡單請求不同的是如果沒有通過預檢,就不會發送 Request。

預檢

發送預檢請求時瀏覽器會先以 OPTIONS method 問候一下後端:「我的 Origin 是 http:example.com,我想要使用 PUT method,另外還想帶上些客製化的 Header。」

OPTIONS /data HTTP/1.1
Origin: http://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header

後端收到 Request 後,可以任意決定要放行的設定:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: PUT, POST, GET, DELETE, OPTIONS
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Max-Age: 86400

只要 Request header 的 OriginAccess-Control-Request- 系列都在 Response header 的列表中,瀏覽器就會發出正式的 Reqeust,注意正式的 Response 依然要有符合的 Headers 才能完成 CORS。

至於 Access-Contorl-Max-Age 則是告訴瀏覽器幾秒之內不用再次預檢,以 86400 來說,就是完成一次預檢後有一天的效力。

 

Credentials

預設 CORS Request 都是匿名(Anonymous)發送,因此想要帶上 Cookies 或是收到 Cookies 需要在前後端都加入一點設定。

前端以 Fetch 為例,無論是簡單還是預檢請求,加上一個設定值後瀏覽器在發出 CORS Request 時就會帶上 Cookies,同時 Response header 中的 Set-Cookie 才會生效:

// Frontend
fetch('https://example.com', { credentials: 'include' })

後端除了要滿足前面提及的 CORS policy 之外,還需要多加一條 Response header Access-Control-Allow-Credentials,否則瀏覽器在收到 Response 時就會直接忽略掉,同時 Set-Cookie header 也不會生效,另外 Access-Control-Allow-Origin 不能是 Wildcard*),需要和 Request 的 Origin 相同:

Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true

在簡單請求的狀況下,收到正確 Response 後一切都會正常運作;在預檢請求的情況下,只有正式 Response 中的 Set-Cookie 才會生效。

 

Cross-Origin !== Cross-Site

做完以上準備後只是告知瀏覽器要傳送和接收 Cookies,但最終的是否實行還是會遵守 Cookie 的 SameSite 屬性,是否為 Cross-site 的判定方式在 Cookies - SameSite Attribute 中有詳細解釋。

當 Cookie 的 SameSite 屬性為 StrictLax 時:

在 Cross-origin 但 Same-site 的情況下加入 Credentials 設定就可以正常送出 Cookies。

如果是 Cross-origin 又 Cross-site,即使做了 Credentials 設定也會因為 Same-Site policy 無法送出 Cookies,需把 Cookie 的屬性設為 SameSite=None; Secure 才能送出。

Cross-site cookie 也稱作 Third-party cookie。

 

crossOrigin 屬性

是否有遇過無法正常讀取字體或是想要取出 Canvas 的內容時出錯(The canvas has been tainted by cross-origin data)的狀況呢?從錯誤訊息可以看出和 CORS 有關。

同樣是因為安全考量,取得這些 CORS 資源時必須把 crossOrigin 屬性設為 anonymous 告訴後端不要回傳 Credentials,同時也告訴瀏覽器這個資源沒有隱私問題,瀏覽器才會放心讀取資源,可以到 Demo 頁面試試看 CORS - Canvas

img.crossOrigin = 'anonymous'

關於字體讀取的規範 Font fetching requirements

<link rel="preload" href="awesome-font.woff2" as="font" type="font/woff2" crossorigin>

Credits

Cross-Origin Resource Sharing (CORS) - MDN
Cross-Origin Resource Sharing (CORS) - web.dev


上一篇
[Day 26] Cookies - SameSite Attribute
下一篇
[Day 28] Device Simulation & Remote Debugging
系列文
你所不知道的各種前端 Debug 技巧30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
Dylan
iT邦新手 3 級 ‧ 2020-11-02 10:24:06

發現手誤:

簡單請求

如果 Request method 是GET、GEAD、POST 其一

GEAD -> HEAD

shizuku iT邦新手 5 級 ‧ 2020-11-03 22:20:54 檢舉

謝謝,已修正囉

我要留言

立即登入留言