必须用CORSMiddleware显式注册,且allow_origins不能填["*"]当allow_credentials=True时;否则浏览器静默拒绝带Cookie或Authorization头的请求。
直接结论:必须用 CORSMiddleware 显式注册,且 allow_origins 不能填 ["*"] 如果你启用了 allow_credentials=True —— 否则浏览器会静默拒绝带 Cookie 或 Authorization 头的请求。
为什么加了中间件还是报 CORS 错误?
常见现象是前端控制台报 Access to fetch at 'http://localhost:8000/api' from origin 'http://localhost:3000' has been blocked by CORS policy,但后端日志里根本没收到请求。
这说明浏览器在发实际请求前卡在了预检(OPTIONS)阶段。根本原因通常是:
-
allow_origins写成了["*"],但同时设了allow_credentials=True—— FastAPI 会直接忽略该中间件配置,不返回任何 CORS 头 - 前端发的是带
Authorization头或Content-Type: application/json的 POST,触发预检,但allow_headers没包含"Authorization"或没放开"Content-Type" - 源写错了:比如前端是
http://localhost:3000,你配了https://localhost:3000(协议不匹配)或漏了端口
CORSMiddleware 必须传哪些参数?
最小可用配置取决于你的前端是否需要传凭证(如登录态 Cookie、Bearer Token)。不传 allow_origins 就等于没开 CORS;其他参数都有默认值,但默认值往往不够用。
立即学习“Python免费学习笔记(深入)”;
生产环境推荐显式声明:
Dreamina
字节跳动推出的AI绘画工具,用简单的文案创作精美的图片
下载
-
allow_origins=["https://your-frontend.com", "https://www.your-frontend.com"]—— 不要用["*"],尤其当涉及登录态时 -
allow_credentials=True—— 只有需要读写 Cookie 或发Authorization头才设为True -
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"]——OPTIONS必须显式列出,否则预检失败 -
allow_headers=["Content-Type", "Authorization", "X-Requested-With"]—— 至少包含前端实际发送的头,["*"]在生产中不被允许(Starlette 会报错)
开发环境怎么快速调试不踩坑?
本地开发时前端常跑在 http://localhost:3000,后端在 http://localhost:8000,这是两个不同源(端口不同),必须配全。
安全又省事的开发配置是:
-
allow_origins=["http://localhost:3000"]—— 精确匹配,避免将来上线漏改 -
allow_credentials=True—— 开发时一般要测登录态,别省这行 -
allow_methods=["*"]—— 开发期方法多变,用通配比手动追加更稳 -
allow_headers=["*"]—— Starlette 允许开发期用["*"],只要allow_origins不是["*"]
示例代码片段:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
容易被忽略的关键点
很多人以为配完中间件就一劳永逸,但实际部署时最容易翻车的是:
- 反向代理(如 Nginx)覆盖了 FastAPI 返回的 CORS 响应头 —— 要么关掉 Nginx 的 CORS 相关设置,要么确保它透传
Access-Control-Allow-*头 - 前端发请求时用了
credentials: "include",但后端allow_credentials是False或allow_origins是["*"],此时浏览器连 OPTIONS 都不发,直接拦截 -
max_age设太小(比如默认 600 秒),导致高频请求反复触发预检,压测时可能暴露性能问题