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

React+Keycloak架构下,iframe内Axios遇401如何请求父组件刷新Token并重试?

解决方案

当然可以实现,核心思路是利用postMessage在iframe子页面和父组件间通信,配合Axios拦截器完成token刷新与请求重发,具体实现步骤如下:

1. 父组件(App.tsx)配置消息监听与token刷新逻辑

父组件需要监听来自可信iframe的token刷新请求,调用Keycloak的刷新方法后将新token返回给子页面:

// App.tsx
import { useEffect } from 'react';
import keycloak from './keycloak'; // 你的Keycloak实例

useEffect(() => {
  const handleMessage = async (event: MessageEvent) => {
    // 验证消息来源,仅处理可信域名的请求
    if (event.origin !== process.env.REACT_APP_IFRAME_DOMAIN) return;

    if (event.data.type === 'REFRESH_TOKEN') {
      try {
        // 调用Keycloak刷新token,5秒内过期则触发刷新
        await keycloak.updateToken(5);
        const newTokens = {
          accessToken: keycloak.token,
          refreshToken: keycloak.refreshToken,
          expiresIn: keycloak.tokenParsed?.exp
        };
        // 向子页面发送新token
        event.source?.postMessage(
          { type: 'TOKEN_REFRESHED', payload: newTokens },
          event.origin
        );
      } catch (error) {
        // 刷新失败,通知子页面跳转登录
        event.source?.postMessage(
          { type: 'TOKEN_REFRESH_FAILED' },
          event.origin
        );
      }
    }
  };

  window.addEventListener('message', handleMessage);
  return () => window.removeEventListener('message', handleMessage);
}, []);

2. 子页面配置Axios拦截器处理401与token刷新

子页面的Axios拦截器在捕获401响应后,向父组件发送刷新请求,等待新token返回后重发原请求,同时添加锁机制避免重复刷新:

// 子页面的Axios配置文件
import axios from 'axios';

let isRefreshing = false;
let failedRequestsQueue: { resolve: (token: string) => void; reject: (error: any) => void }[] = [];

const api = axios.create({
  baseURL: process.env.REACT_APP_API_URL
});

// 请求拦截器:自动添加token到请求头
api.interceptors.request.use(config => {
  const accessToken = localStorage.getItem('accessToken'); // 子页面存储的token
  if (accessToken) {
    config.headers.Authorization = `Bearer ${accessToken}`;
  }
  return config;
});

// 响应拦截器:处理401与token刷新
api.interceptors.response.use(
  response => response,
  async error => {
    const originalRequest = error.config;
    // 仅处理401且未重试过的请求
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

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

      isRefreshing = true;

      // 向父组件发送刷新请求
      return new Promise((resolve, reject) => {
        window.parent.postMessage(
          { type: 'REFRESH_TOKEN' },
          process.env.REACT_APP_PARENT_DOMAIN
        );

        // 监听父组件返回的新token
        const handleTokenResponse = (event: MessageEvent) => {
          if (event.origin !== process.env.REACT_APP_PARENT_DOMAIN) return;

          if (event.data.type === 'TOKEN_REFRESHED') {
            const { accessToken } = event.data.payload;
            // 更新子页面存储的token
            localStorage.setItem('accessToken', accessToken);
            // 更新当前请求的token并重新发起
            originalRequest.headers.Authorization = `Bearer ${accessToken}`;
            resolve(api(originalRequest));
            // 处理队列中等待的所有请求
            failedRequestsQueue.forEach(promise => promise.resolve(accessToken));
            failedRequestsQueue = [];
          } else if (event.data.type === 'TOKEN_REFRESH_FAILED') {
            // 刷新失败,抛出错误并清空队列
            reject(new Error('Token refresh failed, please login again'));
            failedRequestsQueue.forEach(promise => promise.reject(error));
            failedRequestsQueue = [];
          }
          // 移除监听并释放锁
          window.removeEventListener('message', handleTokenResponse);
          isRefreshing = false;
        };

        window.addEventListener('message', handleTokenResponse);
      });
    }

    return Promise.reject(error);
  }
);

export default api;

3. 关键注意事项

  • 域名验证:必须在postMessage和消息监听中验证event.origin,防止跨域恶意攻击,确保仅处理可信域名的通信。
  • 并发请求处理:通过isRefreshing锁和请求队列,避免多个401请求同时触发父组件的token刷新,提升性能并防止重复操作。
  • 刷新失败处理:若Keycloak刷新token失败(比如refresh token也过期),需通知子页面跳转登录,或由父组件统一处理登录逻辑。
  • token存储:子页面可将从父组件获取的token存在localStorage或内存中,请求时自动携带。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 22:43:12