如何修復 CORS 錯誤(以及為什麼會出現)
CORS 錯誤代表你的網頁嘗試讀取來自另一個來源(不同的網域、連接埠或通訊協定)的回應,而該伺服器沒有送出允許讀取的標頭。瀏覽器為保護用戶,會封鎖該回應。修復方法是在伺服器上回傳適用於你網站來源的 Access-Control-Allow-Origin 標頭,並處理 OPTIONS 預檢請求;或者透過你自己的後端、在同一來源上發送請求,從而避免這個錯誤。
這是網頁開發中最令人困惑的錯誤之一,因為請求通常都能順利抵達伺服器。本指南解釋正在發生什麼,以及如何修復每一種情況。
「來源」是什麼意思
來源是通訊協定、網域和連接埠的組合:
| 網址 | 來源 |
|---|---|
https://myapp.com/dashboard | https://myapp.com |
https://api.myapp.com/users | https://api.myapp.com(子網域不同,因此來源不同) |
http://localhost:5173 | http://localhost:5173 |
http://localhost:3000 | http://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 check | OPTIONS 預檢失敗 |
...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.comAccess-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETEAccess-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 請求失敗,或缺少正確的標頭,真正的請求就根本不會送出。