指南

如何修復 CORS 錯誤(以及為什麼會出現)

CORS 錯誤代表你的網頁嘗試讀取來自另一個來源(不同的網域、連接埠或通訊協定)的回應,而該伺服器沒有送出允許讀取的標頭。瀏覽器為保護用戶,會封鎖該回應。修復方法是在伺服器上回傳適用於你網站來源的 Access-Control-Allow-Origin 標頭,並處理 OPTIONS 預檢請求;或者透過你自己的後端、在同一來源上發送請求,從而避免這個錯誤。

這是網頁開發中最令人困惑的錯誤之一,因為請求通常都能順利抵達伺服器。本指南解釋正在發生什麼,以及如何修復每一種情況。

「來源」是什麼意思

來源是通訊協定、網域和連接埠的組合:

網址來源
https://myapp.com/dashboardhttps://myapp.com
https://api.myapp.com/usershttps://api.myapp.com(子網域不同,因此來源不同)
http://localhost:5173http://localhost:5173
http://localhost:3000http://localhost:3000(連接埠不同,因此來源不同)

瀏覽器遵循同源政策:頁面上的 JavaScript 可以自由讀取來自自身來源的回應,但不能讀取其他來源的回應。CORS(跨來源資源共用)則是讓伺服器選擇開放的機制:「我的回應可以被來自這些來源的頁面讀取。」

有兩個結果會令人感到意外:

  • 請求通常會抵達伺服器。 CORS 通常阻止的是你的代碼讀取回應,而不是伺服器接收請求。一個 POST 可能已經被儲存,即使你看到的是錯誤。
  • 只有瀏覽器會強制執行。 curl、Postman 和伺服器端代碼都會完全忽略 CORS。這就是為什麼「在 Postman 裡可以運作」並不能說明任何有關 CORS 的問題。

第一步:閱讀確切的錯誤

開啟開發人員工具(按右鍵 → 檢查),前往 Console,閱讀完整的訊息。Chrome 的字句會告訴你遇到的是哪一種問題:

訊息包含出錯的地方
No 'Access-Control-Allow-Origin' header is present伺服器完全沒有送出該標頭,或請求在到達你的 CORS 代碼之前已經失敗(例如 404 或 500)
The 'Access-Control-Allow-Origin' header has a value '...' that is not equal to the supplied origin伺服器允許的來源與你的來源不同
Response to preflight request doesn't pass access control checkOPTIONS 預檢失敗
...must not be the wildcard '*' when the request's credentials mode is 'include'你正在傳送 Cookie,而伺服器回覆了 *
Request header field authorization is not allowed伺服器沒有在 Access-Control-Allow-Headers 中列出該標頭

然後開啟 Network 分頁,找出失敗的請求(以及之前的任何 OPTIONS 請求),查看它的狀態碼和回應標頭。

先檢查狀態碼。 許多所謂的「CORS 錯誤」其實是偽裝的伺服器錯誤。如果伺服器崩潰或回傳 404,它的錯誤回應往往缺少 CORS 標頭,於是瀏覽器報告的是 CORS,而不是真正的問題。修復那個 500 或 404,CORS 錯誤可能就會消失。

第二步:決定是否應該由瀏覽器直接呼叫該 API

在加入標頭之前,先問:瀏覽器真的應該直接呼叫這個 API 嗎?

  • 需要密鑰的第三方 API(OpenAI、Stripe 的密鑰、大部分電郵服務)絕不應由瀏覽器呼叫。密鑰會暴露給每一位訪客。改為從你的後端呼叫,CORS 問題便會消失,因為伺服器對伺服器的請求不受 CORS 限制。請參閱如何保護 API 金鑰。
  • 你無法控制、又不送出 CORS 標頭的第三方 API,無法從你這一端修復。請從你的後端呼叫。
  • 你自己的 API 可以透過妥善設定來修復。請繼續閱讀下文。

第三步:在伺服器上修復

伺服器必須為其回應加入正確的標頭,包括錯誤回應。

對於實際的請求,送出:

  • Access-Control-Allow-Origin: https://myapp.com(你前端的確切來源,結尾不要斜線)
  • Vary: Origin,如果數值取決於是哪個來源發出的請求

對於預檢 OPTIONS 請求,以成功狀態(200 或 204)回應,並包含:

  • Access-Control-Allow-Origin: https://myapp.com
  • Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE
  • Access-Control-Allow-Headers: Content-Type, Authorization(你送出的每一個自訂標頭)
  • 可選的 Access-Control-Max-Age: 600,讓瀏覽器把預檢結果快取一段時間

大多數框架都有完成這項工作的套件或設定。在 Express 中是 cors 套件;在 FastAPI 中是 CORSMiddleware;在 ASP.NET Core 中是 AddCors;在 Spring 中是 @CrossOrigin 或 CORS 設定。一個典型的 Express 設定如下:

import cors from "cors";

app.use(cors({
  origin: ["https://myapp.com", "http://localhost:5173"],
  credentials: true,
}));

請把 CORS 處理放在路由和身份驗證之前,這樣預檢請求(不帶登入資訊)就不會被 401 拒絕。

如果你使用 AI 建構工具,請把情況描述清楚:

前端位於 https://myapp.com,它呼叫位於 https://api.myapp.com 的 API,並得到「Response to preflight request doesn't pass access control check」。請在 API 上設定 CORS,只允許 https://myapp.com 和預覽來源,允許 GET、POST、PATCH、DELETE,並允許 Content-Type 和 Authorization 標頭,在身份驗證之前處理 OPTIONS,並且也在錯誤回應中包含 CORS 標頭。

第四步:處理 Cookie 和憑證

如果你的前端要把 Cookie(例如登入工作階段)傳送到另一個來源,雙方都必須達成一致:

  • 前端在 fetch 上設定 credentials: "include",或在 axios 中設定 withCredentials: true。
  • 伺服器送出 Access-Control-Allow-Credentials: true。
  • 伺服器的 Access-Control-Allow-Origin 必須是確切的來源,而不是 *。

跨網站的 Cookie 還需要在 Cookie 上設定 SameSite=None; Secure,而某些瀏覽器會進一步限制第三方 Cookie。這也是為什麼應優先採用第五步的做法。

第五步:以同來源設定避免 CORS

通常最乾淨的修復方法,是根本不發出跨來源請求。把 API 放在與頁面相同的來源之下,例如 https://myapp.com/api/...:

  • 在開發時,大部分開發伺服器都能設定代理。Vite 的 server.proxy 設定可以把 /api 轉發到另一個連接埠上的後端,這樣瀏覽器就只會與同一個來源通訊。
  • 在正式環境中,透過主機的重寫或代理設定把 /api 路由到後端;或者讓前端和 API 由同一個伺服器提供。

前端之後會呼叫相對路徑,例如 fetch("/api/orders")。這樣就沒有跨來源請求、沒有跨來源的預檢,也沒有 Cookie 的麻煩。它還意味著你不需要寫死一個在預覽和正式環境之間有所不同的 API 網域。

把前端改為以相對的 /api 路徑呼叫後端,而不是使用完整網址,並設定開發代理,把 /api 轉發到 API 伺服器。然後移除我們已不再需要的任何 CORS 設定。

不要做的事

  • 在 fetch 中使用 mode: "no-cors"。 它並不能繞過任何限制;它只會給你的代碼一個空的、無法讀取的回應。
  • 會關閉 CORS 的瀏覽器擴充功能。 它們只改變你自己的瀏覽器。每一位訪客仍然會看到錯誤。
  • 用於正式應用程式的公開「CORS 代理」服務。 它們可以讀取和更改所有經過它們的內容,而且會停止運作。
  • 在使用登入的 API 上使用 Access-Control-Allow-Origin: *。 它本來就無法與憑證一起使用,而且對於私人資料來說,這是錯誤的預設值。
  • 在允許憑證的情況下,把任何 Origin 標頭原樣反射回去。 這會讓任何網站代表你的用戶發出已登入的請求。請使用允許清單。

AI 建構應用程式中的常見成因

情況修復方法
前端使用密鑰呼叫第三方 API把呼叫移到後端
在預覽中可以運作,但在已發佈的網域上失敗把已發佈的網域和自訂網域來源加入允許清單,或改用相對的 /api 路徑
只在 POST、PUT 或 DELETE 時出錯預檢未有處理;加入 OPTIONS 處理和允許的標頭
加入登入後才出現錯誤憑證需要確切的來源和 Allow-Credentials
出現「CORS 錯誤」,但網絡分頁顯示 500先修復伺服器錯誤
API 網址寫死為 localhost使用相對路徑,或按環境設定

Mythex 中的 CORS

當 Mythex 專案同時有網站和 API 伺服器時,兩者都會以服務的形式在同一專案中運行,你可以在預覽中切換二者。要求代理讓前端透過相對的 /api 路徑呼叫後端,並在開發時由代理轉發,就可以避免大部分 CORS 問題。如果你確實需要跨來源呼叫,請把主控台的確切錯誤和相關來源告訴代理。由於已發佈應用程式的位址與預覽不同,請也在已發佈的應用程式上檢查結果。請參閱文件中的多服務應用程式和 API 後端,以及我們的指南除錯 AI 建構應用程式和前端與後端。

CORS 檢查清單

  • 已閱讀主控台中完整的錯誤,以及失敗請求的狀態碼
  • 已排除隱藏的 404 或 500
  • 需要密鑰的 API 由後端呼叫,而不是由瀏覽器呼叫
  • 伺服器送出帶有確切允許來源的 Access-Control-Allow-Origin
  • OPTIONS 預檢以允許的方法和標頭回應,並在身份驗證之前處理
  • 錯誤回應中也包含 CORS 標頭
  • 憑證:確切的來源、Allow-Credentials: true,以及正確的 Cookie 設定
  • 預覽、已發佈及自訂網域的來源都已涵蓋,或改用相對的 /api 路徑
  • 沒有使用 no-cors、公開代理,或反射任意來源的捷徑

常見問題

CORS 錯誤是什麼意思?

你的頁面從某一個來源載入,卻嘗試讀取另一個來源的回應,而該伺服器的回應中沒有包含允許讀取的標頭。瀏覽器於是阻止你的代碼讀取該回應。修復方法在伺服器端,或在請求的路由方式上,而不是在前端代碼中。

我可以從前端修復 CORS 錯誤嗎?

不能真正地修復。像 mode: 'no-cors' 這類設定只會令你的代碼無法讀取回應,而不會解決問題。正確的做法是讓伺服器送出正確的 Access-Control-Allow-Origin 標頭,或者從你自己的後端或同來源的代理呼叫 API。

Access-Control-Allow-Origin: * 安全嗎?

對於任何人都可以讀取的公開資料,例如不需登入的公開 API,這樣做是可以的。但它不能與 Cookie 或其他憑證一起使用;如果是私人 API,你應該列出確切允許的來源。

為什麼請求在 Postman 或 curl 中可以運作,在瀏覽器中卻不行?

CORS 只由瀏覽器強制執行。Postman 和 curl 等工具不會套用它,所以請求在那裡可以成功,但當頁面的 JavaScript 發出同一請求時,仍會被封鎖。

什麼是預檢(preflight)請求?

對於非簡單的請求,例如帶有 JSON 的 POST,或帶有 Authorization 標頭的請求,瀏覽器會先送出一個 OPTIONS 請求,詢問伺服器允許哪些方法和標頭。如果這個 OPTIONS 請求失敗,或缺少正確的標頭,真正的請求就根本不會送出。

Mythex · Pricing · 用 AI 建造任何嘢 · 網站及應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費 AI 應用程式開發工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(台灣)