ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FastAPI CORS 跨域

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 头。
返回列表