CORS 是什麼?

CORS 是什麼?

Created
Jul 8, 2026 12:47 AM
Summary
CORS 是跨來源資源共用 (Cross-Origin Resource Sharing) 的縮寫。 簡單來說,它是一種網路安全機制,由瀏覽器主動執行。它的目的是決定「是否允許一個網站的網頁,去存取另一個不同來源(網域、通訊協定或連接埠)的伺服器資源」。
Tags
SWE
Note
CORS 是跨來源資源共用 (Cross-Origin Resource Sharing) 的縮寫。
簡單來說,它是一種網路安全機制,由瀏覽器主動執行。它的目的是決定「是否允許一個網站的網頁,去存取另一個不同來源(網域、通訊協定或連接埠)的伺服器資源」。
CORS 限制是瀏覽器的行為。伺服器與伺服器之間的通訊(如後端打 API 給後端)不受此限制。

為什麼CORS 很重要?

過去,當網際網路技術剛出現時,會發生跨站請求偽造 (CSRF) 問題。發生該問題時,虛假的用戶端請求從受害者的瀏覽器傳送到另一個應用程式。
例如,受害者登入其銀行應用程式。然後被誘騙在新的瀏覽器標籤中載入外部網站。然後,外部網站使用受害者的 cookie 憑證並將資料轉送給銀行應用程式,假裝是受害者。這樣未經授權的使用者便能夠存取銀行應用程式。
為了防止此類 CSRF 問題,所有瀏覽器現在都實作同源政策(Same-Origin Policy, SOP)。

同源政策(Same-Origin Policy, SOP)

同源政策必須同時滿足以下三個條件:
  1. 通訊協定(Protocol) 相同(例如都是 https
  1. 網域(Domain) 相同(例如都是 example.com
  1. 連接埠(Port) 相同(例如都是 80443
以下以一些 URL 為例,比較它們與用戶端 URL http://store.aws.com/dir/page.html 的來源情況。
URL
結果
推理
http://store.aws.com/dir2/new.html
同源
只是路徑不同
http://store.aws.com/dir/inner/other.html
同源
只是路徑不同
https://store.aws.com/page.html
不同源
協定不同
http://store.aws.com:81/dir/page.html
不同源
連接埠不同 (http:// 的連接埠預設為 80)
http://news.aws.com/dir/page.html
不同源
不同主機

COR 如何運作?

在標準的網際網路通訊中,瀏覽器向應用程式伺服器傳送 HTTP 請求,接收 HTTP 回應,並顯示相關資料。在瀏覽器術語中,當前瀏覽器 URL 稱為當前來源,第三方 URL 稱為跨來源
當您發起跨來源請求時,請求與回應的過程如下:
  1. 瀏覽器在請求中新增一個來源標頭,其中包含有關當前來源的協定、主機和連接埠的資訊
  1. 伺服器檢查當前的來源標頭並使用請求的資料和 Access-Control-Allow-Origin 標頭進行回應
  1. 瀏覽器看到存取控制請求標頭並然後與用戶端應用程式分享返回的資料
但如果伺服器不允許跨來源存取,它會返回一條錯誤訊息。
由於並非所有CORS都是惡意行為,例如:
  • 前後端分離架構(SPA + API)時,前端呼叫自家後端 API
  • 使用CDN、第三方API
  • 微服務(Microservices)之間的資源共享
  • 企業內部開發與測試環境

跨來源資源共用案例

以 https://news.example.com 這個網站為例。此網站想要透過 API 存取位於 partner-api.com 的資源。
https://partner-api.com 的開發人員首先在其伺服器上設定跨來源資源共用 (CORS) 標頭,方法是將 new.example.com 新增到允許的來源清單。他們透過將以下內容新增到他們的伺服器組態檔案來做到這一點。
Access-Control-Allow-Origin: https://news.example.com
設定 CORS 存取後,news.example.com 便可以從 partner-api.com 請求資源。對於每個請求,partner-api.com 將以 Access-Control-Allow-Credentials : "true" 進行回應。 然後瀏覽器便會知道相關通訊已得到授權並允許跨來源存取。
如果您想要向多個來源授予存取權限,請使用逗號分隔的清單,您還可以使用萬用字元 (如 *) 來向所有人授予存取權限。
 

CORS 的運作機制

CORS 分為 簡單請求(Simple Request) 和預檢請求(Preflight (OPTIONS) Request)兩種流程
在 HTTP 中,請求方法是用戶端希望伺服器執行的資料操作。常見的 HTTP 方法包括 GETPOSTPUT 和 DELETE
在常規的跨來源資源共用 (CORS) 互動中,瀏覽器會同時傳送請求和存取控制標頭。這些請求通常是 GET 資料請求,這類請求被認為是低風險的。
然而,一些 HTTP 請求被認為是複雜的,在請求實際傳送之前需要伺服器確認。預核准程序稱為預檢請求

複雜的跨來源請求

如果跨來源請求使用以下任何一項,則屬於複雜請求:
  • GETPOST 或 HEAD 之外的方法
  • Accept-Language、Accept 或 Content-Language 之外的標頭
  • multipart/form-dataapplication/x-www-form-urlencoded 或 text/plain 之外的 Content-Type 標頭
例如,刪除或修改現有資料的請求被認為是複雜請求。

簡單請求(Simple Request)

滿足特定條件(如 GET, POST, HEAD 方法,且無自訂標頭,Content-Type 僅限 text/plain, multipart/form-data, application/x-www-form-urlencoded
sequenceDiagram autonumber participant FE as Frontend (Browser) participant BE as Backend Server Note over FE: User triggers cross-origin request<br/>(e.g., GET /api/user) FE->>BE: HTTP GET (with automatic Header -> Origin: https://frontend.com) Note over BE: Server processes request and checks Origin BE-->>FE: HTTP 200 OK<br/>with Access-Control-Allow-Origin: https://frontend.com alt Headers Match Note over FE: Browser validation passed.<br/>Data passed to JavaScript. else Headers Mismatch (or Missing) Note over FE: Browser blocks the response data<br/>and throws CORS Error in Console. end
Simple Request Mechanism
  • 前端發起請求: 使用者在網頁上觸發跨域動作(例如透過 GET 獲取使用者資料),瀏覽器會在 HTTP 請求標頭(Header)中,自動加上發起網域的標記 Origin: https://frontend.com
  • 後端伺服器處理: 伺服器接收到請求後照常處理,並在回應(Response)中加入 CORS 相關標頭,告訴瀏覽器它允許哪些網域存取,例如:Access-Control-Allow-Origin: https://frontend.com
  • 瀏覽器最終檢查(分歧點):
    • 若標頭吻合(Headers Match): 瀏覽器確認後端回傳的允許網域包含前端的 Origin,於是通過驗證,將資料交給前端的 JavaScript 程式碼。
    • 若標頭不吻合(Headers Mismatch): 如果後端沒有設定、或者設定的網域不匹配,瀏覽器就會在接收端硬生生將資料攔截,前端程式拿不到資料,並在控制台(Console)噴出 CORS 錯誤。

預檢請求(Preflight (OPTIONS) Request))

不符合簡單請求條件者(如使用 PUT, DELETE,或帶有 Authorization 標頭、Content-Typeapplication/json)。
sequenceDiagram autonumber participant FE as Frontend (Browser) participant BE as Backend Server Note over FE: Triggers Non-Simple Request<br/>(e.g., PUT /api/user with JSON payload) Note over FE, BE: [Phase 1: Preflight Request] FE->>BE: HTTP OPTIONS (Asks if Method: PUT & Custom Headers are allowed) Note over BE: Server validates if the Origin<br/>is allowed for this operation BE-->>FE: HTTP 200 OK<br/>with Access-Control-Allow-Methods/Headers/Origin Note over FE, BE: [Phase 2: Actual Request] alt Preflight Allowed FE->>BE: HTTP PUT (Sends the actual data and request) BE-->>FE: HTTP 200 OK (Returns actual data) Note over FE: Frontend successfully receives data else Preflight Denied Note over FE: Browser aborts the actual request,<br/>returns CORS Error immediately. end
Preflight (OPTION) Request
當遇到較複雜的請求(如 application/jsonPUT/DELETE 方法)時,為了安全性,瀏覽器會將整個通訊拆分為兩個階段:
  • 【階段一:預檢請求 (Preflight Request)】
      1. 發送探路請求: 瀏覽器不會直接送出真正的資料,而是先自動發送一個 HTTP OPTIONS 請求給伺服器,詢問:「我等一下想用 PUT 方法和某些自訂標頭送資料,你接受嗎?」
      1. 伺服器安全審查: 伺服器檢查這個來源與操作是否在允許的白名單內,若允許,則回傳 200 OK,並帶上允許的方法與標頭限制(Access-Control-Allow-Methods/Headers/Origin)。
  • 【階段二:正式請求 (Actual Request)】(分歧點)
    • 預檢通過(Preflight Allowed): 瀏覽器確認伺服器允許該操作,接著才會發送「真正的」PUT 請求與 JSON 資料,伺服器處理完後回傳正式資料,前端順利接收。
    • 預檢拒絕(Preflight Denied): 如果第一階段伺服器就說不允許,瀏覽器會直接中斷後續動作,完全不會發送真正的 PUT 請求,並立刻回報 CORS 錯誤。這能有效保護伺服器不被執行未授權的敏感操作。

CORS 的 Cookies 操作

跨來源請求在傳輸憑證(如 Session ID, Auth Cookie)時,必須同時滿足瀏覽器底層的 SameSite 機制、前端宣告、以及後端安全標頭。若其中一項不符,瀏覽器將拒絕發送或攔截回應。
sequenceDiagram autonumber participant FE as Frontend (Browser) participant BE as Backend Server Note over FE: Step 0: SameSite Check alt Cookie is Lax or Strict Note over FE: Browser blocks Cookie else Cookie is None and Secure alt withCredentials is FALSE FE->>BE: HTTP Request (NO Cookies) BE-->>FE: HTTP 200 OK Response else withCredentials is TRUE FE->>BE: HTTP Request (WITH Cookies) BE-->>FE: HTTP 200 OK Response Note over FE: Browser Security Check alt Missing Credentials Header FE->>FE: CORS Error (Missing Header) else Origin is Wildcard FE->>FE: CORS Error (Wildcard Not Allowed) else Origin Matches Exactly FE->>FE: Validation Passed end Note over FE: JS Reads Session Data end end

Step 0:瀏覽器底層 SameSite 阻擋檢查

當前端網頁(https://frontend.com)透過 JavaScript 對跨來源後端伺服器(https://api.com)發起請求時,在請求離開瀏覽器前,瀏覽器會先檢查該目標網域 Cookie 的 SameSite 屬性:
  • 阻擋情況: 若 Cookie 被設定為 SameSite=Lax(現代瀏覽器預設值)或 Strict,且該跨域請求是由前端 AJAX(如 fetchaxios)觸發,瀏覽器在此步驟會直接扣留 Cookie,不予發送。
  • 放行條件: 後端在核發 Cookie 時,必須明確宣告為 SameSite=None; Secure,該 Cookie 才能參與後續的跨域傳輸。
通過 SameSite 檢查後,流程依據前端程式碼的設定分為兩大分支:
分支 A:前端 withCredentials 為 FALSE(Default)
  • Step 1. (Frontend -> Backend Server): 前端未在程式碼中開啟憑證傳輸(如 Fetch 未設定 credentials: 'include')。瀏覽器在發送前自動拔除 Cookie,發送不帶憑證的匿名請求。
  • Step 2. (Backend Server -> Frontend): 後端伺服器接收請求後,由於未收到 Cookie,將其視為匿名訪客處理,並正常回傳 200 OK 響應。前端雖拿到資料,但無法執行涉及會員身分的操作。
分支 B:前端 withCredentials 為 TRUE
  • Step 3. (Frontend -> Backend Server): 前端明確開啟允許憑證設定(如 Axios 設定 withCredentials: true)。瀏覽器允許帶著該後端網域的 Cookie 跨域發送請求。
  • Step 4. (Backend Server -> Frontend): 後端伺服器成功讀取 Cookie 中的身分驗證資訊,依據該會員權限處理私密資料(如個人訂單),並回傳 200 OK 響應給瀏覽器。

瀏覽器的安全審查與分歧

當實體資料從後端回傳(Step 4. 完成)並抵達使用者電腦後,由於涉及敏感的隱私 Cookie,瀏覽器在將資料交給前端 JavaScript 之前,會在底層觸發安全性審查(Browser Security Check),此處產生三個獨立的執法分歧:
  • Step 6. (Frontend -> Frontend) [觸發關卡 A 失敗 —— 缺少 Credentials 標頭]: 瀏覽器檢查後端回傳的 Response Header,發現缺少 Access-Control-Allow-Credentials: true
    • 結果: 瀏覽器判定此跨域存取不安全,立刻觸發內部攔截機制,將整份回傳資料銷毀。前端 JavaScript 報錯且無法讀取內容。
  • Step 6. (Frontend -> Frontend) [觸發關卡 B 失敗 —— Origin 為萬用字元 ]: 伺服器雖設定了 Credentials: true,但後端的 Access-Control-Allow-Origin 設定為萬用字元 。
    • 結果: 瀏覽器判定此設定具備安全漏洞(任何惡意網站皆能利用此管道配合 withCredentials 讀取隱私資料)。在網頁安全規範中,「憑證傳輸」與「萬用字元(*)」絕對互斥,瀏覽器直接攔截回應並拋出 CORS 錯誤。
  • Step 7. (Frontend -> Frontend) [兩道安全關卡皆通過 —— Origin 精準匹配]: 後端伺服器同時宣告允許憑證(true),且其 Access-Control-Allow-Origin 明確指定了與前端完全吻合的具體網域(如 https://frontend.com)。
    • 結果: 瀏覽器安全審查通過,放行回應資料。
  • Step 8. (最終結果): 前端 JavaScript 順利讀取到帶有身分驗證的私密資料,跨域 Cookie 操作成功。

CORS 和 JSONP 的區別

JSON with Padding (JSONP) 是一種歷史悠久的技術,它允許在不同網域上執行的 Web 應用程式之間進行通訊。
使用 JSONP 時,在用戶端頁面中使用 HTML 指令碼標籤(<script>)。指令碼標籤會載入外部 JavaScript 檔案或直接在 HTML 頁面中嵌入 JavaScript 程式碼。由於指令碼不受同源政策的約束,便可以透過 JavaScript 程式碼擷取跨來源的資料。

JSONP 的核心運作機制

由於瀏覽器的同源政策限制了 fetchaxios 等 AJAX 請求,早期開發者利用了 <script><img> 等標籤的 src 屬性不受同源政策限制的特性來傳遞資料。
  • 前端發起: 在 HTML 中動態插入 <script src="https://api.com/data?callback=handleResponse"></script>
  • 後端回應: 伺服器不能回傳純 JSON,必須回傳一個 JavaScript 函數呼叫,並將 JSON 資料當作參數傳入,例如:handleResponse({"status": "success"})
  • 瀏覽器執行: 瀏覽器下載該腳本後立刻執行,前端預先寫好的 handleResponse 函式隨即被觸發,藉此撈到資料。

JSONP 的缺點

  • 僅支援 GET 方法: 由於本質是利用 <script src="..."> 載入外部檔案,其 src 屬性無法攜帶其他 HTTP 方法的資料,因此只能使用 GET。這導致前端完全無法使用 POST、PUT 或 DELETE 等方法來變更後端狀態。
  • 資料格式受限: 傳輸的資料必須是 JSON 格式。
  • 安全性低(XSS 攻擊風險): JSONP 不如跨來源資源共用 (CORS) 安全,因為提供的資料安全與否全憑外部網域的可信度。使用 JSONP 等同於無條件執行外部網域回傳的任何 JavaScript 程式碼。如果該外部網域遭到駭客入侵、或該網域本身就不可信,駭客就能輕易在網頁上執行惡意腳本,偷走使用者的 Cookie 或機密資料。
  • 錯誤處理極其困難: 因為不是正式的 AJAX 請求,當後端發生 404 Not Found 或 500 Internal Server Error 時,<script> 標籤只會靜默失敗(或者觸發全域的 onerror),前端難以精準捕捉到 HTTP 狀態碼來做對應的防錯處理。

現代的替代方案:CORS

隨著網頁安全規範的演進,現代瀏覽器新增了一些安全功能並進行了更嚴格的安全限制(例如阻擋未加密的跨域腳本、更嚴格的 Cookie 政策),因此包含 JSONP 的舊程式碼已不再適用。
當前全球的網頁標準是一律採用 CORS(跨來源資源共用)。CORS 透過正式的 HTTP 回應標頭(Headers)讓後端自主宣告允許的網域,不僅安全、支援所有 HTTP 方法(GET、POST、PUT、DELETE 等),且擁有完善的預檢請求(Preflight)機制,這才徹底解決了跨網域傳輸的安全難題。

參考

  • MDN Web DocsMDN Web Docs跨來源資源共享(CORS) - HTTP | MDN