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

React中JWT认证(访问/刷新令牌)与全局告警系统实现问询

React + Djoser JWT认证优化与全局告警实现方案

一、JWT令牌刷新机制优化(解决重复请求、竞态条件、自动重试)

核心思路是通过状态锁+请求队列控制刷新流程,避免重复发起刷新请求,同时将等待中的请求暂存,刷新成功后批量重试。

1. 核心实现逻辑

  • 维护isRefreshing标记,防止并发刷新请求
  • 用failedRequestsQueue队列暂存等待刷新完成的请求
  • 在Axios响应拦截器中捕获401 token过期错误,触发刷新流程
  • 刷新成功后更新token,重试队列和原请求;失败则清空token并跳转登录

2. 代码实现(Axios实例配置)

// axiosInstance.js
import axios from 'axios';

const axiosInstance = axios.create({
  baseURL: '/api',
});

let isRefreshing = false;
let failedRequestsQueue = [];

// 请求拦截器:自动携带access token
axiosInstance.interceptors.request.use(
  (config) => {
    const accessToken = localStorage.getItem('access');
    if (accessToken) {
      config.headers.Authorization = `Bearer ${accessToken}`;
    }
    return config;
  },
  (error) => Promise.reject(error)
);

// 响应拦截器:处理token过期
axiosInstance.interceptors.response.use(
  (response) => response,
  async (error) => {
    const originalRequest = error.config;
    // 判定为token过期的401错误(匹配Djoser返回的错误特征)
    if (error.response?.status === 401 && error.response.data.detail?.includes('token') && !originalRequest._retry) {
      originalRequest._retry = true; // 标记已重试,避免循环

      if (isRefreshing) {
        // 正在刷新,将请求加入队列等待
        return new Promise((resolve, reject) => {
          failedRequestsQueue.push({ resolve, reject });
        }).then((token) => {
          originalRequest.headers.Authorization = `Bearer ${token}`;
          return axiosInstance(originalRequest);
        }).catch((err) => Promise.reject(err));
      }

      isRefreshing = true;
      try {
        // 调用Djoser的refresh接口
        const refreshToken = localStorage.getItem('refresh');
        const refreshRes = await axiosInstance.post('/auth/jwt/refresh/', { refresh: refreshToken });
        const newAccessToken = refreshRes.data.access;
        
        // 更新本地token
        localStorage.setItem('access', newAccessToken);
        // 更新当前请求的token
        originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;

        // 重试队列中所有等待的请求
        failedRequestsQueue.forEach(({ resolve }) => resolve(newAccessToken));
        failedRequestsQueue = [];

        // 重试原请求
        return axiosInstance(originalRequest);
      } catch (refreshError) {
        // 刷新失败:清空token,跳转登录页
        localStorage.removeItem('access');
        localStorage.removeItem('refresh');
        window.location.href = '/login';

        // 拒绝队列中所有请求
        failedRequestsQueue.forEach(({ reject }) => reject(refreshError));
        failedRequestsQueue = [];
        return Promise.reject(refreshError);
      } finally {
        isRefreshing = false;
      }
    }
    return Promise.reject(error);
  }
);

export default axiosInstance;

二、全局告警系统(结构化处理嵌套/非嵌套错误)

通过React Context实现全局告警能力,自动解析API返回的不同格式错误,支持全组件调用。

1. 核心实现逻辑

  • 用Context封装告警状态和操作方法
  • 区分处理三种常见API返回格式:
    • 非嵌套错误:{detail: "错误信息"}
    • 嵌套字段错误:{errors: {email: ["格式错误"]}}
    • 成功消息:{message: "操作成功"}
  • 支持自动关闭告警,提供手动关闭入口

2. 代码实现

告警Context与Provider

// AlertContext.jsx
import { createContext, useContext, useState } from 'react';

const AlertContext = createContext();

export const AlertProvider = ({ children }) => {
  const [alerts, setAlerts] = useState([]);

  // 添加告警:支持success/error/warning三种类型
  const addAlert = ({ type = 'error', message }) => {
    const id = Date.now();
    setAlerts(prev => [...prev, { id, type, message }]);
    // 5秒后自动关闭告警(可配置)
    setTimeout(() => removeAlert(id), 5000);
  };

  const removeAlert = (id) => {
    setAlerts(prev => prev.filter(alert => alert.id !== id));
  };

  // 结构化处理API错误
  const handleApiError = (error) => {
    let errorMessage = '请求失败,请稍后重试';
    if (error.response?.data) {
      const data = error.response.data;
      // 处理Djoser表单嵌套错误
      if (data.errors) {
        errorMessage = Object.values(data.errors).flat().join('\n');
      }
      // 处理非嵌套错误(如token过期、权限不足)
      else if (data.detail) {
        errorMessage = data.detail;
      }
      // 处理字符串类型错误
      else if (typeof data === 'string') {
        errorMessage = data;
      }
    }
    addAlert({ type: 'error', message: errorMessage });
  };

  // 处理成功消息
  const handleSuccessMessage = (message) => {
    addAlert({ type: 'success', message });
  };

  return (
    <AlertContext.Provider value={{ addAlert, removeAlert, handleApiError, handleSuccessMessage }}>
      {children}
    </AlertContext.Provider>
  );
};

export const useAlert = () => useContext(AlertContext);

告警展示组件

// AlertComponent.jsx
import { useAlert } from './AlertContext';

const AlertComponent = () => {
  const { alerts, removeAlert } = useAlert();

  return (
    <div className="fixed top-4 right-4 z-50 space-y-2">
      {alerts.map(alert => (
        <div 
          key={alert.id} 
          className={`px-4 py-3 rounded-lg ${
            alert.type === 'success' ? 'bg-green-100 text-green-800' : 
            alert.type === 'error' ? 'bg-red-100 text-red-800' : 
            'bg-yellow-100 text-yellow-800'
          } flex justify-between items-center`}
        >
          <span>{alert.message}</span>
          <button 
            onClick={() => removeAlert(alert.id)}
            className="ml-4 text-xl font-bold hover:opacity-70"
          >
            ×
          </button>
        </div>
      ))}
    </div>
  );
};

export default AlertComponent;

3. 全局使用示例

// App.jsx
import { AlertProvider } from './AlertContext';
import AlertComponent from './AlertComponent';
import axiosInstance from './axiosInstance';
import { useAlert } from './AlertContext';

// 业务组件中调用示例
const UserProfile = () => {
  const { handleApiError, handleSuccessMessage } = useAlert();

  const updateProfile = async (formData) => {
    try {
      await axiosInstance.put('/auth/users/me/', formData);
      handleSuccessMessage('个人信息更新成功');
    } catch (error) {
      handleApiError(error);
    }
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      updateProfile(new FormData(e.target));
    }}>
      {/* 表单内容 */}
      <button type="submit">保存修改</button>
    </form>
  );
};

function App() {
  return (
    <AlertProvider>
      <AlertComponent />
      <UserProfile />
      {/* 其他业务组件 */}
    </AlertProvider>
  );
}

export default App;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 08:37:39