后端重定向至前端时如何安全传递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'); } }
注意事项
- 生产环境必须使用HTTPS,否则
secure=True和samesite="none"会被浏览器拒绝。 - 跨域场景下,
samesite="none"是强制要求,否则Cookie无法跨域传递。 - HttpOnly Cookie是生产环境的最优选择,能最大程度降低安全风险。
内容的提问来源于stack exchange,提问作者mij
相关产品推荐
相关产品推荐

