為什麼還要再寫一篇 CORS#
基於安全考量,瀏覽器以同源政策(Same-Origin Policy)限制網頁存取其他 Origin 的資源(AJAX、DOM、Cookie、圖片等)。CORS (Cross-Origin Resource Sharing) 則提供了一條合法管道,讓符合條件的請求能合法跨過同源政策。
本章從 debug 視角彙整流程與排錯重點。從前端工程整體脈絡的整理,可參考
01-frontend/05-frontend-engineering/07-cors,兩篇互補閱讀。
Cross-Origin 與錯誤訊息#
只要兩個網址的 Scheme、Host、Port 任一不同,就算 Cross-Origin。當瀏覽器擋下跨域回應時,Console 會顯示類似這段訊息:
Access to fetch at [url] from origin [origin] has been blocked by CORS policy: 理由值得注意的是:請求其實已經送出、伺服器也回了 Response,只是因為違反 CORS 規則,瀏覽器不允許 JavaScript 讀取內容。
啟動 CORS 的兩種模式#
CORS 分為簡單請求(Simple Request)與預檢請求(Preflight Request)兩種,是否成功多半取決於後端設定。前端能做的,往往只是看著錯誤訊息提醒後端調整。
共通的首要條件#
瀏覽器會在請求中自動加上 Origin Header,後端必須回傳對應的 Access-Control-Allow-Origin:
Access-Control-Allow-Origin: * # 任意來源都接受
Access-Control-Allow-Origin: http://example.com # 僅允許特定 Origin簡單請求#
當 Method 為 GET、HEAD、POST 三者之一,且 Content-Type 屬於以下其中一種時,視為簡單請求,後端只要回對 Access-Control-Allow-Origin 即可:
application/x-www-form-urlencodedmultipart/form-datatext/plain
完整規則請參考 MDN 的 Simple requests ↗ 章節。
預檢請求(Preflight Request)#
只要不滿足簡單請求條件——例如使用 PUT、DELETE,或 Content-Type 是 application/json——瀏覽器在送出實際請求前,會先發一次預檢。預檢沒過,就根本不會送出真正的請求。
預檢時,瀏覽器以 OPTIONS 先打招呼:
OPTIONS /data HTTP/1.1
Origin: http://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header後端據此決定要開放哪些設定:
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只要請求宣告的 Origin 與 Access-Control-Request-* 都在允許清單內,瀏覽器才會送出正式請求;正式 Response 仍須回對應的 Headers,CORS 才算完成。
Access-Control-Max-Age 則告訴瀏覽器這份預檢結果可快取多久(單位為秒),上例的 86400 表示一天內不再重新預檢。
帶 Cookie 的跨域請求#
預設情況下,CORS 請求是匿名(Anonymous)發送,Cookie 不會被帶上、Response 的 Set-Cookie 也不會生效。要走「跨域帶身份」流程,前後端都要做設定。
前端:宣告需要 Credentials#
以 Fetch 為例,無論是簡單還是預檢請求,加上 credentials: 'include' 後,瀏覽器送出時就會附帶 Cookie,Response 的 Set-Cookie 也才有效:
fetch("https://example.com", { credentials: "include" });後端:補上 Allow-Credentials 並收緊 Origin#
後端除了原本的 CORS 設定,還必須回 Access-Control-Allow-Credentials: true,否則瀏覽器收到 Response 會直接忽略。同時,Access-Control-Allow-Origin 不能再是萬用字元 *,必須與請求的 Origin 完全一致:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Credentials: true簡單請求情境下,正式 Response 對了就一切順利;預檢情境下則只有正式 Response 的 Set-Cookie 會生效。
Cross-Origin 不等於 Cross-Site#
把 Credentials 都打開,僅僅是「讓瀏覽器知道你需要帶 Cookie」。最終是否真的送出,仍要看 Cookie 的 SameSite 屬性。判定 Same-Site 的細節可參考 01-samesite。
當 Cookie 的 SameSite 是 Strict 或 Lax:
- Cross-Origin 但 Same-Site:補上 Credentials 設定即可送出 Cookie。
- Cross-Origin 又 Cross-Site:即使設了 Credentials,也會被 Same-Site 規則擋下;要送出,必須將 Cookie 標記為
SameSite=None; Secure。
Cross-Site Cookie 也就是俗稱的第三方 Cookie(Third-Party Cookie)。
crossOrigin 屬性#
開發時可能遇過字體載不了,或想把跨域圖片畫到 Canvas 上時被擋下並出現 The canvas has been tainted by cross-origin data 錯誤——這些錯誤都和 CORS 相關。
要讓這類資源走 CORS 通道讀取,必須把 crossOrigin 屬性設為 anonymous。這同時告訴後端「不必回傳 Credentials」、告訴瀏覽器「這份資源沒有隱私問題」,瀏覽器才會放心讓 JavaScript 進一步處理:
img.crossOrigin = "anonymous";字體預載的對應寫法可參考 W3C 的 Font fetching requirements ↗:
<link
rel="preload"
href="awesome-font.woff2"
as="font"
type="font/woff2"
crossorigin
/>原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10251693