FastAPI动态Origin的CORS预检请求失败问题求助
动态CORS中间件处理OPTIONS预检请求失败问题
问题场景
做FastAPI后端时,需要允许预定义Origin列表和数据库里动态加载的Origin发起CORS请求。但前端调用GET接口时,浏览器发的OPTIONS预检请求会因为CORS错误失败,导致实际的GET请求根本没发起。
错误原因
原代码的问题集中在OPTIONS请求处理逻辑和动态Origin验证的时机上:
- OPTIONS请求分支里直接创建了JSONResponse,但后续针对数据库Origin的验证逻辑没覆盖这个分支,导致数据库中存在的Origin在OPTIONS请求时加不上正确的CORS响应头。
- 数据库Origin验证通过后,重复调用了
await call_next(request),会让请求被处理两次,OPTIONS请求还会被错误地二次处理。 - 当Origin不在默认列表且数据库验证不通过时,直接抛出HTTPException,但OPTIONS请求应该返回符合CORS规范的响应,而非抛出异常,不然浏览器会判定为CORS错误。
修复后的代码
from fastapi import FastAPI, HTTPException, Request, Response from fastapi.responses import JSONResponse from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from starlette.types import ASGIApp origins = [ "http://localhost:3000", "http://localhost:3001", "http://localhost:8006", ] class DynamicCORSMiddleware(BaseHTTPMiddleware): def __init__(self, app: ASGIApp): super().__init__(app) async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: origin = request.headers.get("origin") # 非浏览器请求(无Origin头)直接跳过CORS处理 if not origin: return await call_next(request) # 先统一验证Origin是否合法 is_allowed = False if origin in origins: is_allowed = True else: # 提取域名(移除协议和端口) domain = origin.replace("https://", "").replace("http://", "") # 检查数据库中是否存在该域名(注意这里要用await,因为MongoDB的find_one是异步方法) domain_exists = await domain_collection.find_one({"$or": [{"domain.main_domain": domain}, {"domain.sub_domain": domain}]}) if domain_exists: is_allowed = True # 处理OPTIONS预检请求 if request.method == "OPTIONS": if not is_allowed: return JSONResponse(content={"detail": "Origin not authorized"}, status_code=403) # OPTIONS请求返回空响应即可,不需要业务内容 response = Response() else: if not is_allowed: raise HTTPException(status_code=403, detail="Origin not authorized") # 非OPTIONS请求正常处理业务逻辑 response = await call_next(request) # 合法Origin才添加CORS响应头 if is_allowed: response.headers["Access-Control-Allow-Origin"] = origin response.headers["Access-Control-Allow-Credentials"] = "true" response.headers["Access-Control-Allow-Headers"] = "*" response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS" response.headers["Vary"] = "Origin" return response app = FastAPI( title="API", description="API documentation for backend", version="1.0.0", docs_url=None, redoc_url=None ) app.add_middleware(DynamicCORSMiddleware)
关键修复点
- 提前统一验证Origin:先完成默认列表和数据库的Origin验证,确保OPTIONS和非OPTIONS请求用同一套规则。
- 正确处理OPTIONS请求:直接返回空Response,避免多余内容;验证不通过时返回标准403响应,符合浏览器预检预期。
- 避免重复处理请求:仅在非OPTIONS且Origin合法时调用一次
call_next,防止请求被重复执行。 - 异步方法修正:数据库查询用
await,保证异步逻辑正确执行。
内容的提问来源于stack exchange,提问作者Rooban
相关产品推荐
相关产品推荐

