You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 22:31:02