FastAPI跨域资源共享
一、为什么会有跨域问题?—— 同源策略详解
在前后端分离项目中,跨域问题是高频痛点,其根源是浏览器的「同源策略(Same-Origin Policy,SOP)」—— 一种浏览器内置的安全机制,用于限制不同源的网页之间的资源交互,防止恶意网站窃取用户敏感数据(如 Cookie、Token、个人信息)。
1.1 同源的判定标准
一个「源」由协议、域名、端口 三个部分共同组成,三者必须完全一致,才被浏览器判定为「同源」;任一部分不同,即为「跨源」。
| 基准地址(示例) | 对比地址 | 是否同源 | 原因 |
|---|---|---|---|
| https://www.example.com:443 | https://www.example.com | 是 | 默认端口443可省略,协议、域名一致 |
| http://www.example.com | 否 | 协议不同(https vs http) | |
| https://blog.example.com | 否 | 域名不同(www vs blog) | |
| https://www.example.com:8080 | 否 | 端口不同(443 vs 8080) |
1.2 同源策略的核心限制
同源策略主要限制非同源网页的以下交互:
- JavaScript 脚本无法读取非同源网页的 Cookie、LocalStorage、SessionStorage;
- 无法通过 AJAX(fetch、axios 等)请求非同源服务器的资源,浏览器会拦截响应;
- 无法访问非同源网页的 DOM 元素。
注意:同源策略是「浏览器的限制」,而非服务器的限制。通过 Postman、curl 等工具发起跨域请求,不会被拦截,可正常获取响应。
1.3 跨域需求的必要性
现代 Web 应用几乎都是前后端分离架构(前端部署在 localhost:8080,后端部署在 localhost:8000,或前端部署在域名 A,后端部署在域名 B),必然需要跨源请求。此时,就需要通过 CORS 机制,让服务器授权浏览器放行跨域请求。
二、CORS 核心原理
CORS(Cross-Origin Resource Sharing,跨域资源共享)是基于 HTTP 响应头的解决方案,核心逻辑是:服务器通过返回特定的跨域响应头,告知浏览器「允许哪些源、哪些方法、哪些请求头访问本服务器资源」,浏览器验证通过后,放行跨域请求的响应。
2.1 两种跨域请求类型
浏览器会根据请求的类型,将跨域请求分为「简单请求」和「预检请求」,两种请求的处理流程不同,需重点区分。
(1)简单请求(无需预检)
同时满足以下条件的请求,属于简单请求,浏览器会直接发起请求,无需提前询问服务器:
- 请求方法:仅 GET、POST、HEAD 三种;
- 请求头:仅包含浏览器默认头,如 Content-Type(仅允许 application/x-www-form-urlencoded、multipart/form-data、text/plain 三种值)、Accept、Accept-Language 等;
- 无自定义请求头,无请求体(或请求体格式符合上述 Content-Type 要求)。
处理流程:前端发起请求 → 服务器返回响应(携带跨域头) → 浏览器验证跨域头 → 验证通过,展示响应;验证失败,拦截响应。
(2)预检请求(OPTIONS 请求,需提前校验)
不满足简单请求条件的,均为预检请求,浏览器会先发起一次 OPTIONS 请求(预检),询问服务器「是否允许该跨域请求」,服务器允许后,才会发起真正的业务请求。
常见触发预检请求的场景:
- 请求方法:PUT、DELETE、PATCH 等非简单请求方法;
- 请求头:包含自定义头(如 Authorization、Token、X-Request-ID);
- 请求体格式:Content-Type 为 application/json(最常见的场景);
- 请求中携带 Cookie/认证信息。
处理流程:前端发起 OPTIONS 预检请求 → 服务器返回 200 状态码(携带跨域授权头) → 浏览器验证预检响应 → 验证通过,发起真正的业务请求 → 服务器返回业务响应 → 浏览器展示响应。
2.2 核心跨域响应头
服务器返回的跨域响应头,是浏览器判断是否放行的核心依据,常用头如下:
Access-Control-Allow-Origin:最核心的头,指定允许访问的跨域源(如 http://localhost:8080),*表示允许所有源(仅开发环境可用);Access-Control-Allow-Methods:允许的请求方法(如 GET, POST, PUT, DELETE, OPTIONS);Access-Control-Allow-Headers:允许的请求头(如 Content-Type, Authorization);Access-Control-Allow-Credentials:是否允许携带 Cookie/认证信息(true/false);Access-Control-Max-Age:预检请求的缓存时间(秒),缓存期间,相同的预检请求不会重复发起,提升性能。
三、前端跨域测试代码
创建 cors-test.html 文件,通过 fetch 发起两种类型的跨域请求(简单请求、预检请求),用于验证后端跨域配置是否生效,直接用浏览器打开即可运行。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>FastAPI CORS 跨域测试</title>
<style>
button { margin: 10px; padding: 8px 16px; cursor: pointer; }
#result { margin-top: 20px; font-size: 14px; line-height: 1.5; }
.success { color: green; }
.error { color: red; }
</style>
</head>
<body>
<h1>FastAPI CORS 跨域测试</h1>
<button onclick="testSimpleRequest()">1. 测试简单请求(GET)</button>
<button onclick="testPreflightRequest()">2. 测试预检请求(POST-JSON)</button>
<div id="result"></div>
<script>
// 后端接口地址(FastAPI 运行地址,需与后端一致)
const baseUrl = 'http://127.0.0.1:8000';
const resultDom = document.getElementById('result');
// 测试简单请求(GET,无需预检)
async function testSimpleRequest() {
try {
const response = await fetch(`${baseUrl}/info`, { method: 'GET' });
const data = await response.text();
resultDom.innerHTML = `✅ 简单请求成功:<br/>${data}`;
resultDom.className = 'success';
} catch (error) {
resultDom.innerHTML = `❌ 简单请求失败:<br/>${error.message}`;
resultDom.className = 'error';
}
}
// 测试预检请求(POST + JSON 头,触发 OPTIONS 预检)
async function testPreflightRequest() {
try {
const response = await fetch(`${baseUrl}/submit`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // 触发预检的关键
body: JSON.stringify({ name: 'FastAPI', type: 'CORS测试' })
});
const data = await response.json();
resultDom.innerHTML = `✅ 预检请求成功:<br/>${JSON.stringify(data, null, 2)}`;
resultDom.className = 'success';
} catch (error) {
resultDom.innerHTML = `❌ 预检请求失败:<br/>${error.message}`;
resultDom.className = 'error';
}
}
</script>
</body>
</html>
四、FastAPI 跨域解决方案
FastAPI 提供两种跨域解决方案:手动实现 CORS 中间件(适合理解底层原理)、使用官方封装的 CORSMiddleware(推荐生产环境使用),两种方法均可实现跨域支持,按需选择。
方法1:手动实现 CORS 中间件
通过 FastAPI 原生中间件,手动拦截 OPTIONS 预检请求、为所有响应添加跨域头,适合深入理解 CORS 工作流程,代码可直接复制运行。
from fastapi import FastAPI, Request
from fastapi.responses import Response, JSONResponse
# 1. 创建 FastAPI 应用实例
app = FastAPI(title="FastAPI 手动实现 CORS", version="1.0.0")
# 2. 自定义 CORS 中间件(核心逻辑)
@app.middleware("http")
async def custom_cors_middleware(request: Request, call_next):
"""
手动实现 CORS 中间件
1. 拦截 OPTIONS 预检请求,直接返回授权响应
2. 为所有业务请求的响应,添加跨域头
"""
# 第一步:处理 OPTIONS 预检请求
if request.method == "OPTIONS":
# 预检请求无需执行后续路由逻辑,直接返回 200 + 跨域授权头
cors_headers = {
"Access-Control-Allow-Origin": "*", # 开发环境允许所有源
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS", # 允许所有方法
"Access-Control-Allow-Headers": "Content-Type, Authorization", # 允许常用请求头
"Access-Control-Max-Age": "86400" # 预检请求缓存 24 小时,避免重复预检
}
return Response(status_code=200, headers=cors_headers)
# 第二步:处理普通业务请求(GET/POST/PUT/DELETE 等)
# 调用下一个中间件或路由函数,获取业务响应
response = await call_next(request)
# 为业务响应添加跨域头
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true" # 允许携带 Cookie
return response
# 3. 测试接口(用于验证跨域)
# 简单请求接口(GET,无需预检)
@app.get("/info")
async def get_info():
return "跨域简单请求测试成功!这是 FastAPI 后端返回的内容"
# 预检请求接口(POST + JSON,触发 OPTIONS 预检)
@app.post("/submit")
async def submit_data(data: dict):
return JSONResponse(
content={
"code": 200,
"msg": "跨域预检请求测试成功",
"data": data
}
)
# 4. 运行服务
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
# 运行后,后端地址:http://127.0.0.1:8000
方法2:使用官方 CORSMiddleware
FastAPI 内置了 fastapi.middleware.cors.CORSMiddleware 中间件,封装了完整的 CORS 逻辑,无需手动处理 OPTIONS 请求,配置简单、功能完善,是生产环境的首选方案。
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
# 1. 创建 FastAPI 应用实例
app = FastAPI(title="FastAPI 官方 CORS 中间件", version="1.0.0")
# 2. 配置并添加官方 CORS 中间件(核心步骤)
app.add_middleware(
CORSMiddleware,
# 关键配置参数(按环境按需调整)
allow_origins=["*"], # 允许的跨域源(开发环境用 *,生产环境替换为具体前端域名)
allow_credentials=True, # 是否允许携带 Cookie/认证信息
allow_methods=["*"], # 允许的请求方法(* 表示所有)
allow_headers=["*"], # 允许的请求头(* 表示所有)
max_age=86400, # 预检请求缓存时间(秒),24小时
)
# 3. 定义数据模型(用于接收 POST-JSON 参数)
class SubmitData(BaseModel):
name: str
type: str
# 4. 测试接口(与手动实现一致,用于验证跨域)
# 简单请求接口
@app.get("/info")
async def get_info():
return "跨域简单请求测试成功!这是 FastAPI 后端返回的内容"
# 预检请求接口
@app.post("/submit")
async def submit_data(data: SubmitData):
return {
"code": 200,
"msg": "跨域预检请求测试成功",
"data": data.dict()
}
# 5. 运行服务
if __name__ == "__main__":
import uvicorn
uvicorn.run("main:app", host="127.0.0.1", port=8000, reload=True)
五、开发/生产环境配置差异
跨域配置的核心风险的是「源的授权范围」,开发环境为了效率可宽松配置,生产环境必须严格限制,避免安全漏洞。
5.1 开发环境配置(宽松,提升效率)
开发环境中,前端通常运行在 localhost:8080/8081,后端运行在 localhost:8000,可使用宽松配置,无需频繁修改:
# 开发环境 CORS 配置(官方中间件)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # 允许所有源
allow_credentials=True,
allow_methods=["*"], # 允许所有方法
allow_headers=["*"], # 允许所有请求头
max_age=86400,
)
5.2 生产环境配置(严格,保障安全)
生产环境中,前端部署在具体域名(如 https://web.xxx.com),后端部署在 API 域名(如 https://api.xxx.com),需严格限制源、方法、头,禁止使用 * 通配符。
# 生产环境 CORS 配置(官方中间件,推荐)
app.add_middleware(
CORSMiddleware,
# 1. 允许的源:仅添加前端实际部署的域名(多个域名用列表)
allow_origins=["https://web.xxx.com", "https://m.xxx.com"],
# 2. 允许携带 Cookie/Token(按需开启)
allow_credentials=True,
# 3. 允许的方法:仅开放业务所需的方法(避免不必要的方法暴露)
allow_methods=["GET", "POST", "PUT", "DELETE"],
# 4. 允许的请求头:仅开放业务所需的请求头
allow_headers=["Content-Type", "Authorization"],
# 5. 预检缓存时间:1小时(3600秒),按需调整
max_age=3600,
)
生产环境关键注意事项
- 禁止 allow_origins=["*"]:通配符
*会允许所有域名访问,存在恶意网站伪造请求的风险,必须替换为具体的前端域名; - allow_credentials=True 的限制:当该参数设为 True(允许携带 Cookie/Token)时,allow_origins 不能为
*,必须指定具体域名,否则浏览器会拦截响应; - 按需限制方法和请求头:无需开放所有方法和请求头,仅开放业务必需的(如仅允许 GET/POST 方法,仅允许 Content-Type/Authorization 请求头),降低攻击面;
- 中间件注册顺序:CORSMiddleware 需注册在「所有路由和其他中间件之前」,确保所有请求都能被跨域中间件拦截处理。
六、跨域测试步骤(快速验证)
无论使用哪种方法,均可通过以下步骤快速验证跨域配置是否生效:
- 运行 FastAPI 后端代码,确保服务正常启动(地址:http://127.0.0.1:8000);
- 用浏览器直接打开
cors-test.html文件(前端运行在本地文件协议,与后端跨源); - 分别点击两个测试按钮:
- 若显示绿色成功提示,说明跨域配置生效;
- 若显示红色失败提示,检查后端配置(如跨域头是否添加、中间件是否注册正确)和接口地址是否匹配。
- (可选)打开浏览器 F12 开发者工具 → Network 面板,可查看请求详情(如 OPTIONS 预检请求的响应状态、跨域头是否存在)。
七、常见跨域问题排查(高频痛点)
- 问题1:预检请求(OPTIONS)返回 404/405 原因:未正确处理 OPTIONS 请求,或手动实现中间件时遗漏了 OPTIONS 分支; 解决:使用官方 CORSMiddleware(自动处理 OPTIONS),或在自定义中间件中优先判断
request.method == "OPTIONS"并返回 200。 - 问题2:携带 Cookie 时跨域失败,提示「The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*'」 原因:allow_credentials=True 时,allow_origins 设为
*,浏览器禁止该组合; 解决:将 allow_origins 改为具体的前端域名(如 ["https://web.xxx.com"])。 - 问题3:自定义请求头(如 Authorization)跨域失败 原因:allow_headers 未包含该自定义请求头,或设置为非
*时遗漏; 解决:在 allow_headers 中添加该请求头(如 ["Content-Type", "Authorization"])。 - 问题4:前端框架(Vue/React/Angular)跨域失败,Postman 测试正常 原因:前端框架的请求默认携带自定义头(如 axios 默认 Content-Type 为 application/json),触发预检请求,后端未配置对应的跨域头; 解决:使用官方 CORSMiddleware,确保 allow_methods 和 allow_headers 配置正确(开发环境可先用
*测试)。 - 问题5:跨域配置后,响应仍被拦截 原因:中间件注册顺序错误(CORSMiddleware 注册在路由之后),部分请求未被拦截; 解决:将
app.add_middleware(CORSMiddleware, ...)移到所有路由注册之前。

浙公网安备 33010602011771号