1. 为什么你的前端连不上FastAPI后端?
最近帮朋友调试一个前后端分离项目时,遇到个典型问题:前端运行在http://localhost:3000,后端API在http://localhost:8000,浏览器死活不让前端访问后端数据。这种场景下,控制台会报类似这样的错误:
Access to fetch at 'http://localhost:8000/api/data' from origin 'http://localhost:3000' has been blocked by CORS policy这个问题困扰过无数开发者,我自己刚接触前后端分离开发时也踩过这个坑。当时花了整整一下午查资料才搞明白,原来浏览器有个叫同源策略的安全机制在作祟。
同源策略就像小区的门禁系统,默认只允许同源访问(协议+域名+端口完全相同)。比如前端在http://localhost:3000,后端在http://localhost:8000,虽然域名相同但端口不同,浏览器就会阻止这种跨域请求。
2. CORS中间件配置全解析
2.1 最简配置方案
要让FastAPI支持跨域,最简单的配置方式如下:
from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["*"], # 允许所有来源 allow_methods=["*"], # 允许所有方法 allow_headers=["*"], # 允许所有头 )这种配置虽然简单,但存在严重安全隐患。我在测试环境曾用过这种配置,结果被安全扫描工具检测出漏洞。生产环境绝对不要使用通配符(*),而应该明确指定允许的源。
2.2 生产环境推荐配置
对于生产环境,我推荐这样配置:
origins = [ "https://your-production-domain.com", "https://www.your-production-domain.com", "https://staging.your-domain.com" ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["Authorization", "Content-Type"], max_age=86400, # 预检请求缓存24小时 )这里有几个关键点需要注意:
allow_credentials=True时,allow_origins不能包含通配符- 明确限制允许的HTTP方法,避免不必要的风险
- 设置较长的max_age可以减少预检请求次数
2.3 参数详解与安全考量
CORSMiddleware的主要参数对安全性有直接影响:
| 参数 | 默认值 | 安全建议 | 风险提示 |
|---|---|---|---|
| allow_origins | [] | 明确列出允许的源 | 使用*会导致CSRF风险 |
| allow_methods | ["GET"] | 按需开放方法 | 开放PUT/DELETE需谨慎 |
| allow_headers | [] | 仅允许必要头 | 开放Authorization需配合其他防护 |
| allow_credentials | False | 按需开启 | 开启后必须指定具体源 |
| max_age | 600 | 生产环境可适当延长 | 过短会影响性能 |
我曾经在一个电商项目中,因为没设置allow_headers导致前端无法发送Authorization头,调试了半天才发现问题。这也说明合理的默认值虽然安全,但可能影响开发体验。
3. 开发环境与生产环境的配置差异
3.1 开发环境特殊配置
开发时经常需要处理localhost跨域问题,我的常用配置是:
origins = [ "http://localhost", "http://localhost:3000", "http://127.0.0.1", "http://127.0.0.1:3000", ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )为了方便调试,开发环境可以放宽限制,但要注意:
- 不要将开发配置部署到生产环境
- 即使开发环境,也建议明确列出允许的源而非使用*
- 可以考虑通过环境变量区分不同环境的配置
3.2 环境感知的智能配置
在实际项目中,我通常会实现自动化的环境检测:
import os def get_cors_config(): if os.getenv("ENV") == "production": return { "allow_origins": ["https://example.com"], "allow_methods": ["GET", "POST"], "allow_credentials": True } else: # 开发环境 return { "allow_origins": ["*"], "allow_methods": ["*"], "allow_credentials": False } app.add_middleware(CORSMiddleware, **get_cors_config())这种模式既保证了开发便利性,又确保了生产环境的安全性。我曾经忘记切换环境配置导致生产环境出现问题,后来就养成了自动化区分的习惯。
4. 常见问题排查与解决方案
4.1 预检请求(OPTIONS)问题
预检请求是CORS中最容易出问题的环节。有一次我们的前端同事突然报告所有POST请求都失败了,查看Network面板发现OPTIONS请求返回404。原因是Nginx配置中没有正确处理OPTIONS方法。
解决方案是在Nginx中添加:
location / { if ($request_method = OPTIONS) { add_header Access-Control-Allow-Origin "$http_origin"; add_header Access-Control-Allow-Methods "GET, POST, OPTIONS"; add_header Access-Control-Allow-Headers "Authorization"; return 204; } # 其他配置... }4.2 凭证(Cookie)相关问题
当你的前端需要发送Cookie时,必须满足三个条件:
allow_credentials=Trueallow_origins不能包含*- 前端需要设置
credentials: "include"
我曾经遇到一个棘手的bug:Chrome正常但Safari无法发送Cookie。最后发现是Safari对SameSite属性的处理更严格,需要在后端设置:
from fastapi import Response @app.get("/") async def root(response: Response): response.set_cookie( key="token", value="xyz", httponly=True, samesite="none", secure=True )4.3 缓存导致的配置不生效
浏览器会缓存CORS响应,特别是预检请求的结果。有次我修改了后端配置但前端依然报错,就是因为缓存。解决方案有:
- 设置适当的max_age
- 开发时使用无痕模式
- 强制刷新浏览器缓存
5. 安全加固与最佳实践
5.1 防御CSRF攻击
虽然CORS不是CSRF防护机制,但合理配置可以减少风险:
- 永远不要在生产环境使用
allow_origins=["*"] - 对于敏感操作,除了CORS还应添加CSRF Token
- 限制
allow_headers,避免不必要的头
我在金融项目中会额外添加:
app.add_middleware( CORSMiddleware, allow_origins=trusted_origins, allow_methods=["GET", "POST"], expose_headers=["X-CSRF-Token"] )5.2 监控与告警
建议对异常的CORS请求进行监控:
- 记录被拒绝的跨域请求
- 监控Origin头的异常值
- 设置频率限制防止滥用
FastAPI中可以这样实现:
from fastapi import Request @app.middleware("http") async def log_cors(request: Request, call_next): origin = request.headers.get("origin") if origin and origin not in allowed_origins: log.warning(f"Blocked CORS request from {origin}") return await call_next(request)5.3 定期审计配置
我建议每季度检查一次CORS配置:
- 确认允许的源列表是否最新
- 检查是否有不必要的HTTP方法被允许
- 验证凭证设置是否符合当前需求
在微服务架构中,每个服务的CORS配置可能不同,更需要系统化的管理。我们团队现在使用配置中心统一管理这些策略,避免遗漏。