FastAPI部署实战:聊聊CORS跨域那些坑
服务器拥有最终决定权:“我允许谁(Origin)、用什么方法(Methods)、带什么凭证(Credentials)来访问我。好,咱们先来聊聊最让人迷惑的“预检请求”(Preflight Request)。想象一下,你的FastAPI后端是一家只接受预约的高级餐厅(API服务器),而前端应用是想要来吃饭的客人(运行在浏览器里)。- 客人:“老板,我打算带5个人(自定义头部),用支付宝(非简单方法)
第一部分:CORS不是错误,是浏览器的“保安”
想象一下,你的FastAPI后端是一家只接受预约的高级餐厅(API服务器),而前端应用是想要来吃饭的客人(运行在浏览器里)。
如果客人直接从餐厅官网(同源)预约,没问题。但如果客人是从某个外卖平台(不同源)跳转过来想订位,餐厅的保安(浏览器)就会站出来:“且慢!我得先问问餐厅老板,接不接受从你这个平台来的客人。”
这一问一答的过程,就是CORS机制。CORS本身不是错误,而是浏览器实施的一种安全策略,目的是防止恶意网站随意读取你的数据。服务器拥有最终决定权:“我允许谁(Origin)、用什么方法(Methods)、带什么凭证(Credentials)来访问我。”
🔍 第二部分:重点!那个多出来的OPTIONS请求是啥?
好,咱们先来聊聊最让人迷惑的“预检请求”(Preflight Request)。你是不是在浏览器开发者工具里,经常看到一个比你真正的API请求先发出的OPTIONS请求?
这就是浏览器在“正式点餐”前,先递上一份“用餐需求清单”。
- 客人:“老板,我打算带5个人(自定义头部),用支付宝(非简单方法)来吃,行不行?”
- 餐厅(服务器):“行,来吧。”(响应中携带允许的规则)
- 客人收到许可,才发出真正的携带数据和方法的POST/GET请求。
哪些情况会触发预检?简单说就是“不简单”的请求:比如用了PUT、DELETE方法,或者自定义了请求头(如Authorization),或者Content-Type是application/json。
关键中的关键:你的服务器必须能正确处理这个OPTIONS请求,并返回正确的CORS响应头。否则,后续真正的请求就会被浏览器直接拦下。
⚙️ 第三部分:上代码!FastAPI CORS配置三段论
接下来重点来了,怎么在FastAPI里配置?官方推荐使用fastapi.middleware.cors中的CORSMiddleware。下面是我总结的“基础版”、“常见版”和“生产谨慎版”。
🎯 1. 基础版:快速让前端连上(用于本地开发)
这是最常见的写法,但仅建议用于本地开发环境。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# 在添加路由之前,先添加CORS中间件!
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有来源,危险!
allow_credentials=True,
allow_methods=["*"], # 允许所有方法
allow_headers=["*"], # 允许所有头部
)
@app.get("/")
async def main():
return {"message": "Hello World"}
“偷懒一时爽,上线火葬场。” 这个配置虽然能快速解决问题,但allow_origins=["*"]和allow_credentials=True是绝对不能同时在生产环境使用的!这会导致严重的凭据泄露风险。
🎯 2. 常见版:指定前端来源(用于测试/预发布)
更安全的做法是明确列出你信任的前端应用地址。
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000", # 本地开发
"https://test.yourfrontend.com", # 测试环境
"https://staging.yourfrontend.com",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"], # 明确列出
allow_headers=["Authorization", "Content-Type", "Accept"], # 明确列出
max_age=600, # 预检请求结果缓存时间(秒),减轻服务器压力
)
看,这样是不是清晰多了?浏览器来自这些地址的请求才会被放行。
🎯 3. 动态配置版(适合多环境)
实际项目中,不同环境的前端地址不同。我习惯通过环境变量来动态配置。
import os
from typing import List
# 从环境变量读取,用逗号分隔多个origin
ALLOWED_ORIGINS: List[str] = os.getenv("ALLOWED_ORIGINS", "").split(",")
# 如果没配置,本地开发默认允许localhost
if not ALLOWED_ORIGINS or ALLOWED_ORIGINS == ['']:
ALLOWED_ORIGINS = ["http://localhost:3000", "http://127.0.0.1:3000"]
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
然后在生产环境的.env或配置文件中设置:ALLOWED_ORIGINS=https://www.yourproduct.com,https://admin.yourproduct.com
🚨 第四部分:我踩过的坑,希望你绕过去
再说个容易翻车的点。顺序!中间件的顺序很重要!
一定要在app.add_middleware(CORSMiddleware, ...)之后再添加你的自定义中间件或路由。否则,你的自定义中间件可能会在处理请求时因为CORS头还没设置而遇到问题。
另外,当你的前端使用了axios等库,并且请求携带了withCredentials: true(比如发送Cookie或Authorization头)时,后端的allow_origins 不能是通配符"*",必须明确指定域名,否则浏览器会报错。这是安全规范。
还有一个隐藏坑:Nginx/Apache等反向代理的配置。有时候你明明在FastAPI里配对了,但请求还是被挡。这时候记得检查一下你的反向代理层(比如Nginx)是否也添加了CORS相关的响应头,造成冲突或覆盖。通常我们只在应用层(FastAPI)处理CORS就够了。
最后啰嗦一句,CORS是浏览器的策略。如果你用curl、postman直接测试API,是看不到CORS错误的。测试时一定要通过浏览器环境!
更多推荐
所有评论(0)