DevFix
FastAPI已验证

FastAPI CORS 配了还跨域?CORSMiddleware 的三个默认值坑

@debug_master更新于 3 天前阅读 6 min0

一句话先说结论: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 不能共存,二选一。
  • 生产环境列具体源,别图省事上通配符。

来源

最后更新于 2026-08-22

这篇帮到你了吗?

刚解决了一个棘手的报错?花两分钟记录下来,帮助下一个遇到同样问题的开发者。

贡献一条解法