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

Axios+React中JWT令牌刷新时setRequestHeader执行失败

React中Axios令牌刷新拦截器的DOMException问题

场景说明

基于access token和refresh token实现JWT认证机制:请求拦截器确保请求头包含带Bearer令牌的Authorization字段;响应拦截器检查是否需要刷新access token,发送刷新请求并更新Axios实例配置。采用TypeScript编写,代码参考Dave Gray的视频教程。

问题现象

测试时将refresh token有效期设为长期,access token设为5秒。access token过期后请求受保护路由,后端日志显示两个请求均成功:(1)受保护路由返回401,(2)刷新令牌请求成功。但浏览器控制台(Chrome、Safari)抛出DOMException,提示setRequestHeader执行失败,原因是请求头值为无效的函数,涉及Axios库AxiosHeaders.js第119行。

代码示例

const axiosPrivate = axios.create({
  baseURL: BASE_URL,
  headers: { "Content-Type": "application/json" },
  withCredentials: true,
});

interface IRequestConfig extends AxiosRequestConfig {
  sent?: boolean;
}

const useAxiosPrivate = () => {
  const { auth } = useAuth()!;
  const refresh = useRefreshToken();

  React.useEffect(() => {
    const requestInterceptor = axiosPrivate.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        config.headers = config.headers ?? {};
        if (!config.headers["Authorization"]) {
          config.headers["Authorization"] = `Bearer ${auth?.token}`;
        }
        return config;
      },
      async (error: AxiosError): Promise<AxiosError> => {
        return Promise.reject(error);
      }
    );

    const responseInterceptor = axiosPrivate.interceptors.response.use(
      (response: AxiosResponse) => response,
      async (error: AxiosError): Promise<AxiosError> => {
        const prevRequestConfig = error.config as IRequestConfig;
        if (error?.response?.status === 401 && !prevRequestConfig?.sent) {
          const newAccessToken = await refresh();
          prevRequestConfig.sent = true;
          prevRequestConfig.headers = prevRequestConfig.headers!;
          prevRequestConfig.headers[
            "Authorization"
          ] = `Bearer ${newAccessToken}`;
          return axiosPrivate(prevRequestConfig);
        }
        return Promise.reject(error);
      }
    );

    return () => {
      axiosPrivate.interceptors.request.eject(requestInterceptor);
      axiosPrivate.interceptors.response.eject(responseInterceptor);
    };
  }, [auth, refresh]);

  return axiosPrivate;
};

错误信息

DOMException: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': 'function (header, parser) {
    header = normalizeHeader(header);
    if (!header) return undefined;
    const key = findKey(this, header);

    if (key) {
      const value = this[key];

      if (!parser) {
        return value;
      }

      if (parser === true) {
        return parseTokens(value);
      }

      if (_utils_js__WEBPACK_IMPORTED_MODULE_0__["default"].isFunction(parser)) {
        return parser.call(this, value, key);
      }

      if (_utils_js__WEBPACK_IMPORTED_MODULE_0__["default"].isRegExp(parser)) {
        return parser.exec(value);
      }

      throw new TypeError('parser must be boolean|regexp|function');
    }
  }' is not a valid HTTP header field value.

调研情况

仅在GitHub找到类似问题,有PR提示问题可能与Axios读取实例配置的方式有关,但不确定是否为Axios本身问题,需解决思路。

解决思路

核心原因

Axios v1.x版本中,config.headers默认是AxiosHeaders实例(而非普通JavaScript对象),直接对其进行赋值或修改操作时,可能误将AxiosHeaders的内部方法当作请求头值传递,导致浏览器抛出无效头值的异常。

具体修复方案

  1. 将AxiosHeaders转为普通对象后更新
    修改响应拦截器中更新headers的代码,通过扩展运算符将原headers转为普通对象,再添加新的Authorization头:

    // 替换原代码中prevRequestConfig.headers相关行
    prevRequestConfig.sent = true;
    prevRequestConfig.headers = {
      ...prevRequestConfig.headers,
      Authorization: `Bearer ${newAccessToken}`
    };
    
  2. 使用AxiosHeaders的官方API修改头信息
    利用Axios提供的AxiosHeaders.isAxiosHeaders判断类型,调用set方法修改Authorization头,符合Axios的规范:

    import { AxiosHeaders } from 'axios';
    
    // 在响应拦截器内
    const prevRequestConfig = error.config as IRequestConfig;
    if (error?.response?.status === 401 && !prevRequestConfig?.sent) {
      const newAccessToken = await refresh();
      prevRequestConfig.sent = true;
      if (AxiosHeaders.isAxiosHeaders(prevRequestConfig.headers)) {
        prevRequestConfig.headers.set('Authorization', `Bearer ${newAccessToken}`);
      } else {
        prevRequestConfig.headers = {
          ...prevRequestConfig.headers,
          Authorization: `Bearer ${newAccessToken}`
        };
      }
      return axiosPrivate(prevRequestConfig);
    }
    
  3. 优化请求拦截器的headers处理
    请求拦截器中避免直接覆盖config.headers为普通对象,改用set方法操作:

    const requestInterceptor = axiosPrivate.interceptors.request.use(
      (config: AxiosRequestConfig) => {
        if (!config.headers?.has('Authorization')) {
          config.headers?.set('Authorization', `Bearer ${auth?.token}`);
        }
        return config;
      },
      async (error: AxiosError): Promise<AxiosError> => {
        return Promise.reject(error);
      }
    );
    

内容的提问来源于stack exchange,提问作者Lev Pleshkov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 00:37:08