如何仅允许指定前端域名访问FastAPI RESTful服务?
解决方案:限制FastAPI后端仅响应Angular前端请求
核心思路
你的服务面向公众且无登录体系,核心目标是区分浏览器合法前端请求与模拟/爬虫请求,不能仅依赖易伪造的基础请求头,需结合浏览器原生安全机制构建多层校验。
可行方案
1. 严格配置CORS策略
用FastAPI的CORSMiddleware仅允许前端域名,阻断浏览器层面的非法跨域请求:
from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() app.add_middleware( CORSMiddleware, allow_origins=["https://w.domain.com"], # 仅开放前端域名 allow_credentials=True, allow_methods=["*"], allow_headers=["*"], max_age=3600, )
注:此方法仅拦截浏览器跨域,无法阻止curl这类直接发起的请求,需配合其他校验。
2. 校验Origin头+处理预检请求
浏览器跨域请求会自动携带Origin头,后端可在中间件中校验该值,并正确响应OPTIONS预检请求:
from fastapi import Request, HTTPException from starlette.middleware.base import BaseHTTPMiddleware class OriginCheckMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): allowed_origin = "https://w.domain.com" origin = request.headers.get("origin") # 处理浏览器预检请求 if request.method == "OPTIONS": response = await call_next(request) response.headers["Access-Control-Allow-Origin"] = allowed_origin response.headers["Access-Control-Allow-Credentials"] = "true" return response # 非预检请求校验Origin if origin != allowed_origin: raise HTTPException(status_code=403, detail="非法请求来源") response = await call_next(request) response.headers["Access-Control-Allow-Origin"] = allowed_origin return response app.add_middleware(OriginCheckMiddleware)
curl可伪造Origin,但配合下方方案能进一步过滤。
3. 校验浏览器专属Sec-Fetch-*头
浏览器会自动添加Sec-Fetch-*系列请求头,这些头无法通过前端JS修改,仅由浏览器生成:
Sec-Fetch-Site: 跨域场景下取值为cross-siteSec-Fetch-Mode: 跨域请求取值为cors
在中间件中加入校验逻辑:
# 延续上面的OriginCheckMiddleware,在Origin校验后添加 sec_fetch_site = request.headers.get("sec-fetch-site") sec_fetch_mode = request.headers.get("sec-fetch-mode") if sec_fetch_site not in ["cross-site", "same-site"] or sec_fetch_mode != "cors": raise HTTPException(status_code=403, detail="非合法浏览器请求")
普通用户很难知晓这些头的正确取值,能过滤大部分非浏览器发起的恶意请求。
4. 短时效临时Token验证(无登录场景)
Angular前端初始化时向后端请求一个5分钟有效期的临时Token,后续所有API请求携带该Token:
- 前端Angular代码示例:
// 获取临时Token async getTempToken(): Promise<string> { const res = await fetch('https://r.domain.com/api/temp-token', { method: 'GET', credentials: 'include' }); const data = await res.json(); return data.token; } // 携带Token发起请求 async fetchApiData() { const token = await this.getTempToken(); const res = await fetch('https://r.domain.com/api/data', { headers: { 'X-Temp-Token': token } }); // 处理响应逻辑 }
- 后端FastAPI代码示例:
from fastapi import Depends, HTTPException from jose import JWTError, jwt from datetime import datetime, timedelta SECRET_KEY = "your-custom-secret-key" ALGORITHM = "HS256" TOKEN_EXPIRE_MINUTES = 5 def generate_temp_token(): expire = datetime.utcnow() + timedelta(minutes=TOKEN_EXPIRE_MINUTES) encoded_jwt = jwt.encode({"exp": expire}, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt @app.get("/api/temp-token") async def issue_temp_token(request: Request): # 先校验Origin和Sec-Fetch头,确保是合法浏览器请求 origin = request.headers.get("origin") if origin != "https://w.domain.com": raise HTTPException(status_code=403) return {"token": generate_temp_token()} def validate_temp_token(token: str = Depends(...)): try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) if datetime.fromtimestamp(payload["exp"]) < datetime.utcnow(): raise HTTPException(status_code=401, detail="Token已过期") return payload except JWTError: raise HTTPException(status_code=403, detail="无效Token") # 受保护的API路由 @app.get("/api/data") async def get_protected_data(token: dict = Depends(validate_temp_token)): return {"data": "你的业务数据"}
此方法能有效阻挡直接的curl请求,因为获取Token的前置请求会被浏览器头校验拦截。
组合方案推荐
按优先级组合以下校验,最大化拦截非法请求:
- 严格的CORS配置
Origin头校验Sec-Fetch-*头校验- 短时效临时Token验证
多层校验后,既能保证合法用户的正常访问,又能大幅降低非前端来源请求的通过率。
内容的提问来源于stack exchange,提问作者Jane Wayne
相关产品推荐
相关产品推荐

