Expo React Native对接FastAPI后端的CORS处理方法
处理Expo React Native + FastAPI的CORS与接口保护问题
首先要明确:CORS是浏览器专属的安全机制,Expo React Native在开发阶段的调试环境(依赖Expo Go)会有特殊情况,但生产打包后的原生APP(APK/IPA)发起的HTTP请求完全不受CORS规则约束。所以你的核心需求——保护接口不被未授权客户端调用,不能只靠CORS,得结合其他验证手段。
一、开发环境的CORS配置
开发时用Expo Go调试,请求的来源可能是exp://开头的地址、设备本地IP或者localhost,可以这样配置FastAPI的CORS中间件:
from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() # 开发环境允许的来源 origins = [ "http://localhost", "http://localhost:8080", "exp://*", # 允许所有Expo调试地址 "http://192.168.0.0/16", # 允许本地局域网内的所有IP ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], )
注意:开发环境放宽CORS限制是为了调试方便,绝对不要在生产环境使用
"*"或者宽泛的IP范围。
二、生产环境的接口保护方案
因为原生APP不受CORS约束,只靠CORS无法阻止恶意客户端调用你的接口,需要用以下方式加固:
1. 使用API密钥/令牌验证
给你的RN应用嵌入一个专属的API密钥,每次请求时在请求头(比如X-API-Key)里携带这个密钥,FastAPI后端验证密钥的合法性:
# FastAPI后端验证逻辑 from fastapi import FastAPI, Header, HTTPException app = FastAPI() VALID_API_KEY = "你的专属密钥" @app.get("/protected-endpoint") async def protected_route(x_api_key: str = Header(None)): if x_api_key != VALID_API_KEY: raise HTTPException(status_code=401, detail="未授权访问") return {"message": "访问成功"}
RN端请求时添加头:
// Expo RN请求示例 fetch('https://your-api-domain.com/protected-endpoint', { headers: { 'X-API-Key': '你的专属密钥' } })
注意:不要把密钥硬编码在代码里,建议用Expo的
SecureStore存储,或者通过环境变量注入(Expo的app.config.js可以配置环境变量)。
2. 使用OAuth2或JWT令牌
如果你的应用有用户登录系统,可以用JWT令牌做身份验证。用户登录后后端返回JWT,RN端存储令牌,每次请求时在Authorization头里携带:
# FastAPI JWT示例(需要安装python-jose和passlib) from fastapi import FastAPI, Depends, HTTPException, status from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm from jose import JWTError, jwt from passlib.context import CryptContext from datetime import datetime, timedelta SECRET_KEY = "你的JWT密钥" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") app = FastAPI() # 模拟用户数据库 fake_users_db = { "user1": { "username": "user1", "hashed_password": pwd_context.hash("password123"), } } def create_access_token(data: dict, expires_delta: timedelta | None = None): to_encode = data.copy() if expires_delta: expire = datetime.utcnow() + expires_delta else: expire = datetime.utcnow() + timedelta(minutes=15) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt async def get_current_user(token: str = Depends(oauth2_scheme)): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="无法验证凭据", headers={"WWW-Authenticate": "Bearer"}, ) try: payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) username: str = payload.get("sub") if username is None: raise credentials_exception except JWTError: raise credentials_exception user = fake_users_db.get(username) if user is None: raise credentials_exception return user @app.post("/token") async def login(form_data: OAuth2PasswordRequestForm = Depends()): user = fake_users_db.get(form_data.username) if not user or not pwd_context.verify(form_data.password, user["hashed_password"]): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="用户名或密码错误", ) access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_access_token( data={"sub": user["username"]}, expires_delta=access_token_expires ) return {"access_token": access_token, "token_type": "bearer"} @app.get("/user/me") async def read_users_me(current_user: dict = Depends(get_current_user)): return {"username": current_user["username"]}
RN端存储并携带JWT:
// 登录获取令牌后存储 import * as SecureStore from 'expo-secure-store'; async function saveToken(token) { await SecureStore.setItemAsync('userToken', token); } // 请求时携带令牌 async function fetchProtectedData() { const token = await SecureStore.getItemAsync('userToken'); const response = await fetch('https://your-api-domain.com/user/me', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); console.log(data); }
3. 限制请求来源的IP(可选)
如果你的用户群体集中在特定区域,可以在服务器层面(比如Nginx、Cloudflare)配置IP白名单,但这种方式灵活性差,不适用于全球用户的应用。
总结
- 开发环境:通过FastAPI的CORS中间件允许Expo调试相关的来源,方便开发。
- 生产环境:放弃依赖CORS,改用API密钥、JWT等身份验证机制来保护接口,这才是原生APP场景下可靠的安全方案。
内容的提问来源于stack exchange,提问作者Sahl
相关产品推荐
相关产品推荐

