StackTraces
FastAPI已驗證

FastAPI CORS 報 No 'Access-Control-Allow-Origin'

@debug_master32 天前更新閱讀 6 min00

一句話先說結論:CORS 是瀏覽器執行的策略,後端要做的是“把正確的響應頭帶上”。FastAPI 預設不加任何 CORS 頭,必須顯式加 CORSMiddleware。最常見的坑是:沒加中間件、allow_origins 漏了前端源、以及 allow_origins=["*"] 和 allow_credentials=True 撞在一起——這個組合瀏覽器會直接拒掉,而且服務端一點不報錯。

背景

前端跑在 http://localhost:3000,後端跑在 http://localhost:8000,兩者端口不同就是跨域。瀏覽器的同源策略會攔住響應,除非後端在響應頭裡明確放行。現代 REST API 幾乎都會觸發預檢(OPTIONS):只要帶了 Authorization 頭、用了 application/json、或用了 PUT/DELETE,瀏覽器都會先發一個 OPTIONS 問一句“允不允許”。

這也解釋了為什麼 curl/Postman 一切正常、瀏覽器卻報錯——curl 不執行 CORS。

現象

前端控制台:

Access to fetch at 'http://localhost:8000/api/data'
from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.

更隱蔽的一種,當 allow_origins=["*"] 同時開了 allow_credentials=True 時:

The value of the 'Access-Control-Allow-Origin' header in the response
must not be the wildcard '*' when the request's credentials mode is 'include'.

根因分析

FastAPI 的 CORSMiddleware 預設是最保守的,幾個參數不填會踩坑:

  • 沒加中間件:壓根不發任何 CORS 頭。
  • allow_origins 漏源:列表裡沒寫前端源,Access-Control-Allow-Origin 就對不上。
  • allow_methods 預設只有 ['GET']:如果你只寫 allow_origins 不寫 methods,POST/PUT 會被預檢攔下——很多人不知道這個預設值。
  • allow_headers 預設空列表:前端帶 Authorization 這樣的自定義頭,必須顯式列入,否則預檢失敗。
  • allow_origins=["*"] + allow_credentials=True:這是被 Fetch 規範明令禁止的組合。瀏覽器要求“帶憑據”時 Access-Control-Allow-Origin 必須是具體源、不能是 *。這個錯服務端不會報錯,allow_credentials 被靜默忽略,排查起來特別費勁(FastAPI 0.100+ 會在啟動時拋 ValueError,舊版本則靜默放行,反而埋下 CSRF 隱患)。

解決方案

生產:具體 origin + 憑據

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://app.example.com",
        "https://admin.example.com",
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
    expose_headers=["X-Total-Count"],
    max_age=3600,
)

開發 / 公開只讀 API:通配符,但別帶憑據

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],     # 通配符 = 不带凭据
    allow_credentials=False, # 必须 False
    allow_methods=["*"],
    allow_headers=["*"],
)

兩條路只能二選一:想帶 cookie/token,就列具體源;想通配,就別指望憑據。

環境變數管理源列表,注意 strip

用環境變數注入允許源時,記得去空格,否則 "a, b" 裡的 b 因為帶前導空格而匹配不上:

import os

allowed = [o.strip() for o in os.getenv("ALLOWED_ORIGINS", "").split(",") if o.strip()]
if "*" in allowed and len(allowed) > 1:
    raise ValueError("不能把通配符 * 和具体源混在一起")

app.add_middleware(
    CORSMiddleware,
    allow_origins=allowed,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
    allow_headers=["Content-Type", "Authorization"],
)

排查小抄

  • 先看響應頭有沒有 Access-Control-Allow-Origin,沒有就是中間件沒生效或沒加。
  • 再看 allow_origins 列表是否精確包含前端源(協議、域名、端口一個都不能差)。
  • 檢查 allow_methods/allow_headers 是否漏了前端實際用到的方法和頭。
  • 帶 cookie/token 時,確認沒用 ["*"]。
  • 用 TestClient 跑一遍預檢請求,能當場抓住這類問題:
from fastapi.testclient import TestClient

client = TestClient(app)
r = client.options("/api/data", headers={
    "Origin": "http://localhost:3000",
    "Access-Control-Request-Method": "GET",
})
assert "access-control-allow-origin" in r.headers

小結

  • FastAPI 預設無 CORS,必須顯式 add_middleware(CORSMiddleware, ...)。
  • allow_methods 預設只有 GET、allow_headers 預設空,別漏配。
  • allow_origins=["*"] 和 allow_credentials=True 不能共存,二選一。
  • 生產環境列具體源,別圖省事上通配符。

References(參考來源)

最後更新於 2026-09-09

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法