FastAPI退出登录后刷新页面用户自动重新登录的问题求助
FastAPI退出登录后刷新页面用户自动重新登录的问题求助
大家好,我现在在做一个FastAPI+React的项目,用Cookie存储JWT实现会话认证,最近遇到了一个特别头疼的问题:用户点击退出登录后,前端会正常跳转到登录页面,但刷新登录页之后,用户又自动恢复登录状态了!
我排查了好久,感觉要么是退出时Cookie没被正确删除,要么是浏览器还在保留着认证信息,麻烦各位大佬帮忙看看代码里哪里出问题了😭
问题背景
- 后端:FastAPI +
fastapi-login库,用HttpOnly Cookie存储JWT令牌 - 前端:React + Redux Toolkit + Axios,请求时携带
withCredentials: true - 退出逻辑:后端删除认证Cookie和Google OAuth相关Cookie,前端更新Redux状态并跳转登录页
后端代码(FastAPI)
from fastapi import APIRouter, Response, Request, status, Depends from fastapi_login import LoginManager from sqlalchemy.orm import Session # 其他依赖导入... SECRET_KEY = "your-secret-key" TOKEN_URL = "/auth/login" GOOGLE_OAUTH_STATE_COOKIE = "google_oauth_state" GOOGLE_OAUTH_MODE_COOKIE = "google_oauth_mode" expire_duration = ... # 自定义的令牌过期时长 seconds = ... # 自定义的Cookie过期秒数 manager = LoginManager(SECRET_KEY, token_url=TOKEN_URL, use_cookie=True) router = APIRouter(prefix="/auth", tags=["AUTHENTICATION"]) def _clear_auth_cookie(response: Response): response.delete_cookie( key=manager.cookie_name, path="/", httponly=True, samesite="none", secure=True, ) def _set_auth_cookie(response: Response, subject_id: str, role: str, is_active: bool): access_token = manager.create_access_token( data={"sub": subject_id, "role": role, "is_active": is_active}, expires=expire_duration, ) response.set_cookie( key=manager.cookie_name, value=access_token, httponly=True, samesite="none", secure=True, path="/", expires=seconds, ) def _clear_google_oauth_cookies(response: Response): response.delete_cookie( key=GOOGLE_OAUTH_STATE_COOKIE, path="/", httponly=True, samesite="none", secure=True, ) response.delete_cookie( key=GOOGLE_OAUTH_MODE_COOKIE, path="/", httponly=True, samesite="none", secure=True, ) @router.post("/tokenVerification", status_code=status.HTTP_200_OK) async def token(request: Request, response: Response, db: Session = Depends(get_db)): user = await manager.optional(request) if user: if user.role in {"member", "trainer"} and getattr(user, "email_verified", True) is False: _clear_auth_cookie(response) return { "valid": False, "role": "user", "is_active": False, "user_id": None, "is_super_admin": False, "email_verified": False, } user.last_login = func.now() db.commit() if user.role == "member": return { "valid": True, "role": user.role, "is_active": user.is_active, "user_id": user.user_id, "is_super_admin": False, } if user.role == "trainer": return { "valid": True, "role": user.role, "is_active": user.is_active, "user_id": user.trainer_id, "is_super_admin": False, } if user.role == "admin": return { "valid": True, "role": user.role, "is_active": user.is_active, "user_id": user.admin_id, "is_super_admin": bool(user.is_super_admin), } _clear_auth_cookie(response) return _clear_auth_cookie(response) return {"valid": False, "role": "user", "is_active": False, "user_id": None, "is_super_admin": False} @router.post("/logout") def logout(response: Response): _clear_auth_cookie(response) _clear_google_oauth_cookies(response) return {"message": "Logged out successfully"}
前端代码(React + Redux Toolkit + Axios)
authSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import axios from "axios"; const BASE_URL = import.meta.env.VITE_BACKEND_URL; // 异步验证后端会话状态 export const verifyAuth = createAsyncThunk( "auth/verifyAuth", async (_, thunkAPI) => { try { const res = await axios.post(`${BASE_URL}/auth/tokenVerification`, null, { withCredentials: true, }); return res.data ?? { valid: false, role: 'user', is_active: false, user_id: null, is_super_admin: false }; } catch { return thunkAPI.rejectWithValue(false); } } ); const authSlice = createSlice({ name: "auth", initialState: { loggedIn: false, role:'user', is_active:false, user_id:null, is_super_admin: false, }, reducers: { logout(state) { state.loggedIn = false; state.role = 'user'; state.is_active = false; state.user_id = null; state.is_super_admin = false; }, }, extraReducers: (builder) => { builder .addCase(verifyAuth.fulfilled, (state, action) => { const {valid, role, is_active, user_id, is_super_admin} = action.payload; state.loggedIn = !!valid; state.role = role; state.is_active = is_active; state.user_id = user_id; state.is_super_admin = !!is_super_admin; }) .addCase(verifyAuth.rejected, (state) => { state.loggedIn = false; state.role = 'user'; state.is_active = false; state.user_id = null; state.is_super_admin = false; }); }, }); export const { logout } = authSlice.actions; export default authSlice.reducer;
退出登录按钮逻辑
const handleLogout = async () => { try { await axios.post(`${BASE_URL}/auth/logout`, null, { withCredentials: true }) dispatch(logout()) setTimeout(() => { navigate('/login') }, 500); } catch { return; } }
复现步骤
- 正常登录系统,确认处于已登录状态
- 点击退出登录按钮,前端成功跳转到
/login页面 - 刷新
/login页面 - 发现Redux状态被自动更新为登录状态,用户“回到”了系统中
预期行为 vs 实际行为
- ✅ 预期:退出登录后,认证Cookie被彻底删除,刷新页面保持未登录状态
- ❌ 实际:刷新页面后,
verifyAuth请求返回有效会话,用户自动恢复登录
退出登录后的响应头
我查看了/logout接口的响应头,Set-Cookie字段如下:
set-cookie : access-token=""; expires=Sun, 15 Mar 2026 05:05:25 GMT; HttpOnly; Max-Age=0; Path=/; SameSite=none; Secure set-cookie : google_oauth_state=""; expires=Sun, 15 Mar 2026 05:05:25 GMT; HttpOnly; Max-Age=0; Path=/; SameSite=none; Secure set-cookie : google_oauth_mode=""; expires=Sun, 15 Mar 2026 05:05:25 GMT; HttpOnly; Max-Age=0; Path=/; SameSite=none; Secure
我检查了删除Cookie的参数,和设置Cookie时的path、httponly、samesite、secure完全一致,Max-Age也设为0了,但为什么Cookie好像没被真正删掉?或者是不是前端的verifyAuth请求有问题?麻烦帮忙分析一下😭




