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
相关产品推荐
相关产品推荐

