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

React前端如何处理JWT过期导致的未授权(无Token)问题?

解决方案:JWT过期自动登出与状态同步问题修复

第一步:修复后端错误状态码返回问题

你的后端存在两处错误导致前端无法正确捕获401状态:

  1. protect函数中else块的代码顺序错误,先抛出错误再设置状态码,导致状态码不生效
  2. createPost控制器的try-catch覆盖了protect抛出的401错误,强制返回500状态

修改protect函数:

const protect = asyncHandler(async (req, res, next) => {
  let token = req.cookies.jwt;

  if (token) {
    try {
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      req.user = await User.findById(decoded.userId).select('-password');
      next();
    } catch (error) {
      res.status(401);
      throw new Error('Not authorized, token failed');
    }
  } else {
    res.status(401); // 先设置状态码再抛出错误
    throw new Error('Not authorized, no token');
  }
});

修改createPost控制器:

移除多余的try-catch(asyncHandler已自动处理错误传递),同时补全await关键字:

const createPost = asyncHandler(async (req, res) => {
  const { type, text, images } = req.body;
  const newPost = new Post({
    type,
    text,
    images,
    user: req.user._id,
  });
  const post = await newPost.save(); // 补全await,返回实际文档而非Promise
  res.status(200).json(post);
});

第二步:前端全局拦截401错误(RTK Query)

不要在单个请求中处理401,通过RTK Query的全局拦截器统一处理所有请求的权限错误:

自定义带权限校验的baseQuery:

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
import { logout } from '../features/auth/authSlice';
import toast from 'react-hot-toast';

const baseQuery = fetchBaseQuery({
  baseUrl: '/api',
  credentials: 'include', // 确保携带cookie
});

const baseQueryWithAuth = async (args, api, extraOptions) => {
  const result = await baseQuery(args, api, extraOptions);
  
  // 捕获401状态,触发登出逻辑
  if (result.error && result.error.status === 401) {
    api.dispatch(logout());
    toast.error('登录已过期,请重新登录');
  }
  
  return result;
};

export const apiSlice = createApi({
  reducerPath: 'api',
  baseQuery: baseQueryWithAuth,
  tagTypes: ['Posts'],
  endpoints: (builder) => ({
    // 你的接口定义(createPost、getMyPosts等)
  }),
});

第三步:实现登出与自动重定向逻辑

完善authSlice的logout action:

import { createSlice } from '@reduxjs/toolkit';

const initialState = {
  user: null,
};

const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    setUser: (state, action) => {
      state.user = action.payload;
    },
    logout: (state) => {
      state.user = null;
      // 手动设置cookie过期,触发浏览器删除httpOnly cookie
      document.cookie = 'jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
    },
  },
});

export const { setUser, logout } = authSlice.actions;
export default authSlice.reducer;

全局监听用户状态,自动重定向:

在App.js或根组件中添加状态监听:

import { useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { useEffect } from 'react';

function App() {
  const user = useSelector(state => state.auth.user);
  const navigate = useNavigate();

  useEffect(() => {
    if (!user) {
      const currentPath = window.location.pathname;
      // 避免登录页/注册页重复重定向
      if (currentPath !== '/login' && currentPath !== '/register') {
        navigate('/login');
      }
    }
  }, [user, navigate]);

  // ...其他组件渲染逻辑
}

export default App;

第四步:简化createPost的错误处理

有了全局拦截后,单个请求只需处理非401的业务错误:

const postSubmit = async () => {
  if (images && images.length) {
    try {
      const postImages = images.map((img) => dataURItoBlob(img));
      const path = `tyt/${username}/post_images`;
      let formData = new FormData();
      formData.append('path', path);
      postImages.forEach((image) => formData.append('file', image));
      const response = await uploadImages(formData, path);

      // 使用unwrap()获取成功数据或抛出错误
      await createPost({
        type: null,
        text: text,
        images: response,
      }).unwrap();

      refetch();
      setPostVisible(false);
      setText('');
      setImages([]);
    } catch (error) {
      toast.error(error?.data?.message || '发布失败');
    }
  } else if (text) {
    try {
      await createPost({
        type: null,
        text: text,
        images: null,
      }).unwrap();

      refetch();
      setPostVisible(false);
      setText('');
    } catch (error) {
      toast.error(error?.data?.message || '发布失败');
    }
  }
};

修改完成后,当JWT过期时:

  1. 后端会正确返回401状态码
  2. 前端全局拦截器捕获后触发登出,清除Redux用户状态
  3. 根组件监听状态变化,自动重定向到登录页并弹出提示

内容的提问来源于stack exchange,提问作者Stephen Scott Moore

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 10:07:13