FastAPI部署实战:聊聊CORS跨域那些坑
摘要:当你把精心开发的FastAPI应用部署上线,却发现前端页面死活调不通接口,浏览器控制台一片飘红……别慌,这八成是CORS(跨域资源共享)在作祟。本文带你彻底搞懂CORS原理,分享FastAPI中从“允许所有”到“精细化配置”的实战代码,并针对开发、测试、生产不同环境给出方案,最后聊聊那些文档里没写的安全陷阱和常见坑点。
🎯 你的前端应用突然无法调用后端API了?报错里满是“CORS”?别急着怀疑人生,这几乎是每个全栈工程师部署时的“成人礼”。我当年第一次遇到时,也以为服务器炸了,对着日志研究了半天……结果就是个配置项。
更真实的是,根据经验,超过70%的初次部署问题都与CORS配置不当有关。而且很多人即便配通了,也是简单粗暴地允许所有来源(allow_origins=["*"]),为后续安全漏洞埋下伏笔。
📖 主要内容脉络
👉 1. 什么是CORS?用“餐厅订位”的比喻秒懂
👉 2. 浏览器究竟在背后干了啥?(预检请求详解)
👉 3. FastAPI中CORS配置的“三段式”实战代码
👉 4. 开发/测试/生产环境,配置怎么变?
👉 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错误的。测试时一定要通过浏览器环境!
好了,关于FastAPI的CORS,我的经验差不多都倒出来了。别再因为一个通配符配置引发安全事件,希望这篇能帮你省下几个熬夜debug的晚上。
如果你在部署时还遇到过其他奇怪的“拦路虎”,或者有更优雅的配置方案,一定在下面分享出来啊!技术人的成长,不就是靠这样一次次“踩坑”和“填坑”的接力嘛。收藏这篇文章,下次部署前翻出来对照一下,大概率能帮你平稳落地。
— 你的技术老朋友,一名程序媛
浙公网安备 33010602011771号