You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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时的pathhttponlysamesitesecure完全一致,Max-Age也设为0了,但为什么Cookie好像没被真正删掉?或者是不是前端的verifyAuth请求有问题?麻烦帮忙分析一下😭

火山引擎 最新活动