当前位置: 首页 > news >正文

FastAPI CORS 跨域

FastAPI CORS 跨域学习笔记

一、什么是跨域问题

1. 同源策略

浏览器遵循同源策略(Same-Origin Policy),限制一个源的网页向另一个源发送请求。

同源 = 协议 + 域名 + 端口 三者一致

URL AURL B是否同源原因
http://example.com/ahttp://example.com/b协议/域名/端口相同
http://example.comhttps://example.com协议不同
http://example.comhttp://api.example.com域名不同
http://example.comhttp://example.com:8080端口不同

2. 跨域错误表现

前端(http://localhost:3000)请求后端(http://localhost:8000)时,浏览器控制台报错:

Access to XMLHttpRequest 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.

关键:跨域限制是浏览器行为,服务端之间互相调用不受影响。


二、CORS 机制

1. 简单请求

满足以下所有条件的请求为简单请求,浏览器直接发送,在响应中检查 CORS 头:

  • 方法为GETHEADPOST之一
  • Content-Type 仅限于text/plainmultipart/form-dataapplication/x-www-form-urlencoded
  • 无自定义请求头
浏览器 ──GET /api──→ 服务器 浏览器 ←── 响应 + CORS头 ── 服务器 │ ├─ Access-Control-Allow-Origin 包含该源 → 放行 └─ 不包含 → 拦截

2. 预检请求(Preflight)

不满足简单请求条件的请求(如含自定义头、PUT/DELETE方法、application/json),浏览器会先发一个OPTIONS请求:

浏览器 ──OPTIONS /api──→ 服务器 ← 预检请求 浏览器 ←── 200 + CORS头 ── 服务器 ← 预检响应 │ ├─ 预检通过 → 发送实际请求 └─ 预检失败 → 拦截,不发送实际请求 浏览器 ──PUT /api──→ 服务器 ← 实际请求 浏览器 ←── 响应 + CORS头 ── 服务器 ← 实际响应

三、FastAPI 中配置 CORS

1. 基本用法

fromfastapiimportFastAPIfromfastapi.middleware.corsimportCORSMiddleware app=FastAPI()app.add_middleware(CORSMiddleware,allow_origins=["http://localhost:3000","https://example.com"],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],)

2. 参数详解

参数类型默认值说明
allow_originslist[str][]允许的源列表,["*"]表示允许所有源
allow_methodslist[str]["GET"]允许的 HTTP 方法,["*"]表示全部
allow_headerslist[str][]允许的请求头,["*"]表示全部
allow_credentialsboolFalse是否允许携带 Cookie / Authorization
expose_headerslist[str][]允许前端访问的响应头
max_ageintNone预检请求缓存时间(秒)

3. 各参数对应的响应头

参数对应的 CORS 响应头
allow_originsAccess-Control-Allow-Origin
allow_methodsAccess-Control-Allow-Methods
allow_headersAccess-Control-Allow-Headers
allow_credentialsAccess-Control-Allow-Credentials
expose_headersAccess-Control-Expose-Headers
max_ageAccess-Control-Max-Age

四、常见配置场景

1. 开发环境 — 允许所有

app.add_middleware(CORSMiddleware,allow_origins=["*"],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],)

注意allow_origins=["*"]allow_credentials=True不能同时生效。浏览器规范要求携带凭证时Allow-Origin不能为*,FastAPI 会自动忽略allow_credentials

2. 生产环境 — 精确控制

app.add_middleware(CORSMiddleware,allow_origins=["https://www.example.com","https://admin.example.com",],allow_credentials=True,allow_methods=["GET","POST","PUT","DELETE"],allow_headers=["Authorization","Content-Type"],expose_headers=["X-Request-ID"],max_age=600,# 预检缓存 10 分钟)

3. 动态来源 — 从环境变量读取

importos origins=os.getenv("CORS_ORIGINS","http://localhost:3000").split(",")app.add_middleware(CORSMiddleware,allow_origins=[o.strip()foroinorigins],allow_credentials=True,allow_methods=["*"],allow_headers=["*"],)

4. 支持子域名通配

CORSMiddleware不支持*.example.com通配,需自定义逻辑:

fromfastapiimportFastAPI,Requestfromfastapi.middleware.corsimportCORSMiddlewarefromstarlette.responsesimportResponse app=FastAPI()ALLOWED_DOMAINS=["example.com","example.org"]defis_allowed_origin(origin:str)->bool:fromurllib.parseimporturlparse hostname=urlparse(origin).hostnameor""returnany(hostname==dorhostname.endswith(f".{d}")fordinALLOWED_DOMAINS)@app.middleware("http")asyncdefcors_middleware(request:Request,call_next):response:Response=awaitcall_next(request)origin=request.headers.get("origin")iforiginandis_allowed_origin(origin):response.headers["Access-Control-Allow-Origin"]=origin response.headers["Access-Control-Allow-Credentials"]="true"response.headers["Access-Control-Allow-Methods"]="GET, POST, PUT, DELETE, OPTIONS"response.headers["Access-Control-Allow-Headers"]="Authorization, Content-Type"# 处理预检请求ifrequest.method=="OPTIONS":response.status_code=200returnresponse

五、CORS 中间件的工作流程

请求到达 CORSMiddleware │ ├─ 是否为 OPTIONS 预检请求? │ ├─ 是 → 检查 Origin 是否在 allow_origins 中 │ │ ├─ 在 → 返回 200 + CORS 头(不转发到路由) │ │ └─ 不在 → 返回 400 │ │ │ └─ 否 → 继续处理 │ ├─ 检查 Origin 是否在 allow_origins 中 │ ├─ 在 → 转发到路由,响应中添加 CORS 头 │ └─ 不在 → 转发到路由,响应中不添加 CORS 头 │ └─ 浏览器根据响应中的 CORS 头决定是否放行

六、allow_credentials的限制

规则

allow_originsallow_credentials行为
["*"]False允许所有源,不携带凭证
["*"]True无效,浏览器拒绝*+ credentials 组合
具体源列表True正常工作,允许指定源携带凭证

凭证包含什么

  • Cookie
  • HTTP 认证头(Authorization)
  • TLS 客户端证书

前端配合

// axiosaxios.get("/api/data",{withCredentials:true});// fetchfetch("/api/data",{credentials:"include"});

七、常见问题排查

1. 仍然报跨域错误

可能原因

原因排查方式
allow_origins未包含前端源检查浏览器控制台中的 Origin 值
中间件注册顺序问题CORS 中间件应在其他中间件之前注册
Nginx 重复添加 CORS 头检查 Nginx 配置,移除重复的add_header
预检请求被其他中间件拦截确认 OPTIONS 请求未被认证中间件拦截

2. 响应头重复

Access-Control-Allow-Origin: http://localhost:3000 Access-Control-Allow-Origin: http://localhost:3000 ← 重复

原因:Nginx 和 FastAPI 都添加了 CORS 头。解决:只在一处配置。

3. Cookie 无法携带

检查清单:

  • allow_credentials=True
  • allow_origins不是["*"]
  • 前端请求设置了withCredentials: true
  • Cookie 的SameSite属性正确
  • Cookie 的Secure属性与协议匹配(HTTPS 需要Secure

八、完整示例

fromfastapiimportFastAPI,Cookie,Responsefromfastapi.middleware.corsimportCORSMiddleware app=FastAPI()# ---- CORS 配置 ----app.add_middleware(CORSMiddleware,allow_origins=["http://localhost:3000","https://www.example.com",],allow_credentials=True,allow_methods=["GET","POST","PUT","DELETE","OPTIONS"],allow_headers=["Authorization","Content-Type","X-Request-ID"],expose_headers=["X-Request-ID"],max_age=600,)# ---- 路由 ----@app.get("/api/data")asyncdefget_data():return{"message":"Hello from API"}@app.post("/api/login")asyncdeflogin(response:Response):response.set_cookie(key="session_id",value="abc123",httponly=True,samesite="lax",)return{"message":"logged in"}@app.get("/api/profile")asyncdefprofile(session_id:str=Cookie(None)):ifnotsession_id:fromfastapiimportHTTPExceptionraiseHTTPException(status_code=401,detail="Not authenticated")return{"session_id":session_id}

九、注意事项

  1. 生产环境禁止allow_origins=["*"]:应明确列出允许的源,防止恶意网站窃取用户数据。
  2. CORS 不是安全机制:CORS 保护的是浏览器用户,不保护服务器。服务端仍需做认证和鉴权。
  3. 预检请求缓存:设置max_age可减少 OPTIONS 请求次数,降低延迟。
  4. 中间件注册顺序add_middleware先注册的在外层,CORS 应放在最外层(最先注册)。
  5. 反向代理场景:如果使用 Nginx,建议在 Nginx 统一处理 CORS,或确保只有一处添加 CORS 头。
http://www.jsqmd.com/news/773016/

相关文章:

  • 3DS FBI Link终极指南:Mac上最便捷的3DS文件传输工具
  • 从Windows 11到Nano Server:一张图看懂.NET 6与.NET 7的跨平台支持矩阵
  • 别再乱用 String 了!底层原理、常量池、拼接陷阱全解析
  • 2026年5月国内正规市场地位证明机构实测排行与能力解析 - 速递信息
  • 2026年最新市场地位认证技术维度解析与专业机构能力评估 - 速递信息
  • 使用OpenClaw构建AI智能体时配置Taotoken作为提供商
  • MPC-BE开源媒体播放器技术架构深度解析
  • ros2 从零开始19 使用 Node Interfaces 模板类(C++)
  • 2026 年孟德尔·格林伯格分享 OurCar 开发经验:解决家庭共享汽车难题!
  • QQ自定义在线状态改在线源码
  • FastAPI 静态文件
  • 【2026实战】双栈协同:Python+Go混合架构完整实战
  • 解密TlbbGmTool:如何高效管理天龙八部单机版游戏数据的3个核心问题
  • XSLT 实例
  • VS3000芯片深度体验:除了传4K,它的USB和网络功能在视频会议里到底有多香?
  • 高频脉冲电源选购:高性价比靠谱产品筛选策略解析
  • Java 代码质量度量指标:评估代码质量的标准
  • FastAPI 安全认证
  • ComfyUI Manager:AI绘画插件的智能管家,5分钟打造高效创作环境
  • Fast-GitHub加速插件:3步解决国内GitHub访问难题的终极方案
  • 全面解决Kohya_ss安装问题的10个专业技巧:从环境配置到高效训练
  • runprompt:基于Dotprompt格式的命令行LLM提示词工程化与自动化工具
  • Botty终极指南:5步配置暗黑2重制版24小时自动化MF脚本
  • 读源码像读小说?试了 DeepWiki 和 Zread,我再也不想裸读 GitHub 了
  • Moodle自动化工具:零配置API客户端与AI助手集成实战
  • 终极ComfyUI-Manager完全指南:快速部署与高效管理自定义节点
  • Java后端面试:核心基础考点,String、StringBuilder、StringBuffer 区别详解
  • 别再死记硬背了!用Verilog手把手带你理解CRC校验的电路核心(附串行/并行实现代码)
  • 节后系统恢复中的技术操作:批量处理、数据一致性与人机协作
  • 做了一个 App Store 全球最低价查询工具:支持 App、订阅和内购价格对比