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

Angular FormIO表单应用中JWT过期401拦截及刷新令牌实现问询

FormIO JWT 过期自动刷新实现方案

你可以直接基于已接入的FormIO fetch插件扩展响应拦截逻辑,无需额外引入Angular全局拦截器,避免和FormIO内部请求逻辑冲突,具体实现步骤如下:

1. 先定义全局状态控制并发刷新

避免多个请求同时返回401时重复发起refresh token请求,用两个变量做锁和请求队列:

  • 刷新状态锁isRefreshing:标记当前是否正在请求新的JWT
  • 请求队列refreshQueue:存储所有401后待重发的请求回调

2. 扩展FormIO fetch插件的拦截逻辑

你之前的fetch插件应该已经实现了请求拦截加JWT的逻辑,现在补充响应拦截的401处理:

import { Formio } from '@formio/angular';

// 全局状态
let isRefreshing = false;
let refreshQueue: Array<(token: string) => void> = [];

// 初始化fetch插件
Formio.registerPlugin(
  {
    priority: 0,
    // 你已有的请求拦截逻辑,加JWT
    preRequest: (args) => {
      const jwt = localStorage.getItem('access_token');
      if (jwt) {
        args.opts.headers = {
          ...args.opts.headers,
          Authorization: `Bearer ${jwt}`
        };
      }
      return args;
    },
    // 新增响应拦截逻辑
    preResponse: async (args) => {
      const response = args.response;
      // 捕获401状态
      if (response.status === 401) {
        // 先把原请求的配置存下来
        const originalRequest = args.opts;
        const originalUrl = args.url;

        // 如果不在刷新状态,就发起刷新token请求
        if (!isRefreshing) {
          isRefreshing = true;
          try {
            const refreshToken = localStorage.getItem('refresh_token');
            // 调用你的刷新token接口
            const refreshRes = await fetch('/api/auth/refresh', {
              method: 'POST',
              headers: {
                'Content-Type': 'application/json'
              },
              body: JSON.stringify({ refresh_token: refreshToken })
            });

            if (!refreshRes.ok) throw new Error('Refresh token invalid');
            const { access_token: newAccessToken, refresh_token: newRefreshToken } = await refreshRes.json();
            
            // 更新本地存储的token
            localStorage.setItem('access_token', newAccessToken);
            localStorage.setItem('refresh_token', newRefreshToken);
            // 同步更新FormIO全局token,避免内部其他请求用旧token
            Formio.setToken(newAccessToken);
            
            // 执行队列里所有待重发的请求
            refreshQueue.forEach(callback => callback(newAccessToken));
            refreshQueue = [];

            // 重发当前401的原请求
            args.response = await fetch(originalUrl, {
              ...originalRequest,
              headers: {
                ...originalRequest.headers,
                Authorization: `Bearer ${newAccessToken}`
              }
            });
          } catch (e) {
            // 刷新失败说明refresh token也过期了,清空存储跳登录
            localStorage.removeItem('access_token');
            localStorage.removeItem('refresh_token');
            Formio.clearToken();
            window.location.href = '/login';
            return args;
          } finally {
            isRefreshing = false;
          }
        } else {
          // 正在刷新的话,把当前请求加入队列,等刷新完成后再重发
          return new Promise(resolve => {
            refreshQueue.push((newToken: string) => {
              // 刷新完成后用新token重发请求,替换原响应
              fetch(originalUrl, {
                ...originalRequest,
                headers: {
                  ...originalRequest.headers,
                  Authorization: `Bearer ${newToken}`
                }
              }).then(newRes => {
                args.response = newRes;
                resolve(args);
              });
            });
          });
        }
      }
      return args;
    }
  },
  'jwt-interceptor'
);

3. 边界情况处理

  • 若你的refresh接口不需要走FormIO的fetch拦截,直接用Angular的HttpClient调用也可以,注意要在刷新成功后同步更新全局的FormIO token
  • 建议给refresh token加过期时间校验,前端可以先判断refresh token是否已过期,避免无效的接口请求
  • 重发请求时要注意保留原请求的所有参数:method、body、query参数等,不要遗漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 15:09:04