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

后端重定向至前端时如何安全传递JWT Token?

跨域场景下FastAPI OAuth2认证后JWT的优雅传递方案

首先明确:重定向场景下前端无法获取后端响应头,因为浏览器会自动执行跳转,前端JavaScript无法捕获重定向响应的头信息,所以通过请求头传递JWT的方式不可行。优先推荐使用跨域Cookie方案,安全且符合规范。

方案一:HttpOnly跨域Cookie传递JWT(生产环境首选)

HttpOnly Cookie无法被前端JS读取,能有效防止XSS攻击,是最安全的传递方式。需要配合跨域资源共享(CORS)配置实现。

1. 修改FastAPI的/auth回调端点,设置跨域Cookie

替换原重定向带token参数的逻辑,改为在重定向响应中设置Cookie:

@app.get("/auth")
async def auth(
    request: Request, users_repo: UsersRepository = Depends(SQLUsersRepository)
):
    try:
        access_token = await oauth.google.authorize_access_token(request)
    except OAuthError as e:
        raise CREDENTIALS_EXCEPTION
    _data = access_token["userinfo"]
    if get_or_create_user(data=_data, users_repo=users_repo):
        _token = create_token(_data["email"])
        # 构建重定向响应
        response = RedirectResponse(url="http://localhost:5173/auth")
        # 设置跨域Cookie
        response.set_cookie(
            key="access_token",
            value=_token,
            httponly=True,          # 禁止JS读取,防XSS
            secure=False,           # 开发环境用False,生产环境必须改为True(仅HTTPS传输)
            samesite="none",        # 跨域场景强制设置,需配合secure=True
            max_age=3600,           # 和JWT过期时间保持一致(例:1小时)
            domain="localhost"      # 开发环境用localhost,生产环境改为实际域名
        )
        return response
    raise CREDENTIALS_EXCEPTION

2. 配置FastAPI的CORS支持跨域Cookie

必须开启allow_credentials=True,否则浏览器会拒绝跨域Cookie:

from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# 允许的前端源,生产环境替换为实际域名
origins = [
    "http://localhost:5173",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,  # 关键:允许跨域携带Cookie
    allow_methods=["*"],
    allow_headers=["*"],
)

3. Vue端配置请求自动携带Cookie

在请求库(如axios)中开启withCredentials,确保每次接口请求都自动带上Cookie:

// src/api/index.js
import axios from 'axios';

const api = axios.create({
  baseURL: 'http://localhost:8000',
  withCredentials: true, // 强制携带Cookie
});

export default api;

4. Vue的/auth页面验证登录状态

在前端回调页面发起后端验证接口,通过Cookie自动传递JWT,获取用户信息:

后端新增验证端点

from fastapi import Depends, HTTPException, status
from jose import JWTError, jwt
from pydantic import BaseModel

class UserInfo(BaseModel):
    email: str
    # 按需添加其他用户字段

# 与create_token使用相同的密钥和算法
SECRET_KEY = "your-jwt-secret-key"
ALGORITHM = "HS256"

async def get_current_user(request: Request, users_repo: UsersRepository = Depends(SQLUsersRepository)):
    token = request.cookies.get("access_token")
    if not token:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="未认证",
        )
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        email: str = payload.get("sub")
        if not email:
            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效凭证")
    except JWTError:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="无效Token")
    
    user = await users_repo.get_user_by_email(email)
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="用户不存在")
    return UserInfo(email=user.email)

@app.get("/verify-token")
async def verify_token(current_user: UserInfo = Depends(get_current_user)):
    return {"authenticated": True, "user": current_user}

Vue回调页面逻辑

<template>
  <div>正在验证登录状态...</div>
</template>

<script setup>
import { useProfileStore } from '@/stores/profile';
import api from '@/api';
import { useRouter } from 'vue-router';

const profileStore = useProfileStore();
const router = useRouter();

async function checkAuth() {
  try {
    const res = await api.get('/verify-token');
    if (res.data.authenticated) {
      profileStore.setAuthenticated(true);
      profileStore.setUser(res.data.user);
      router.push('/'); // 验证成功跳转到首页
    }
  } catch (err) {
    console.error('登录验证失败:', err);
    router.push('/login'); // 验证失败跳回登录页
  }
}

checkAuth();
</script>

方案二:非HttpOnly Cookie(不推荐生产环境)

如果需要前端直接读取JWT(存在XSS风险),可以关闭httponly参数:

# 在/auth端点的Cookie设置中修改
response.set_cookie(
    key="access_token",
    value=_token,
    httponly=False,  # 允许前端JS读取
    secure=False,
    samesite="none",
    max_age=3600,
    domain="localhost"
)

前端可直接读取Cookie并验证:

function getCookie(name) {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  return parts.length === 2 ? parts.pop().split(';').shift() : null;
}

async function checkAuth() {
  const token = getCookie('access_token');
  if (token) {
    try {
      const res = await api.post('/verify-token-manual', { token });
      if (res.data.valid) {
        // 更新登录状态
        profileStore.setAuthenticated(true);
        router.push('/');
      }
    } catch (err) {
      router.push('/login');
    }
  } else {
    router.push('/login');
  }
}

注意事项

  1. 生产环境必须使用HTTPS,否则secure=True和samesite="none"会被浏览器拒绝。
  2. 跨域场景下,samesite="none"是强制要求,否则Cookie无法跨域传递。
  3. HttpOnly Cookie是生产环境的最优选择,能最大程度降低安全风险。

内容的提问来源于stack exchange,提问作者mij

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 00:50:24