Angular 17 HttpInterceptorFn:Token续期时HTTP请求排队方案咨询
Angular 17 HttpInterceptorFn 并发Token续期问题解决思路
核心思路是通过单例状态管理控制Token刷新流程,用请求队列暂存刷新期间的待处理请求,避免重复刷新并确保所有请求使用最新Token。
1. 封装Token刷新状态管理服务
创建单例服务维护全局唯一的刷新状态与请求队列,统一处理Token续期逻辑:
import { Injectable } from '@angular/core'; import { Observable, throwError } from 'rxjs'; import { catchError, tap } from 'rxjs/operators'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class TokenRefreshService { private isRefreshing = false; private requestQueue: Array<(newToken: string) => void> = []; constructor(private http: HttpClient) {} // 发起后端Token续期请求 refreshAccessToken(): Observable<string> { return this.http.post<{ token: string }>('/api/auth/refresh', {}) .pipe( tap(res => { // 更新本地存储的Token(根据项目实际存储方式调整) localStorage.setItem('authToken', res.token); }), catchError(err => { // 续期失败后重置状态,避免后续请求阻塞 this.resetRefreshState(); return throwError(() => err); }) ); } // 将待处理请求加入队列 enqueueRequest(callback: (newToken: string) => void): void { this.requestQueue.push(callback); } // 遍历队列,用新Token重新发起所有待处理请求 processQueue(newToken: string): void { this.requestQueue.forEach(callback => callback(newToken)); this.requestQueue = []; this.isRefreshing = false; } // 重置刷新状态(错误或完成后调用) resetRefreshState(): void { this.isRefreshing = false; this.requestQueue = []; } // 获取当前刷新状态 getIsRefreshing(): boolean { return this.isRefreshing; } // 设置刷新状态 setIsRefreshing(state: boolean): void { this.isRefreshing = state; } }
2. 在HttpInterceptorFn中实现队列调度逻辑
通过注入单例服务,拦截请求并处理Token过期、队列暂存与重试逻辑:
import { HttpInterceptorFn } from '@angular/common/http'; import { inject } from '@angular/core'; import { TokenRefreshService } from './token-refresh.service'; import { catchError, switchMap } from 'rxjs/operators'; import { Observable, throwError } from 'rxjs'; export const authInterceptor: HttpInterceptorFn = (req, next) => { const tokenService = inject(TokenRefreshService); const currentToken = localStorage.getItem('authToken'); // 实现Token过期校验逻辑(示例:解析JWT的exp字段) const isTokenExpired = () => { if (!currentToken) return true; const payload = JSON.parse(atob(currentToken.split('.')[1])); return payload.exp * 1000 < Date.now(); }; // Token未过期,直接添加授权头发送请求 if (!isTokenExpired()) { const authReq = req.clone({ setHeaders: { Authorization: `Bearer ${currentToken}` } }); return next(authReq); } // Token已过期,判断是否正在刷新 if (tokenService.getIsRefreshing()) { // 正在刷新,将当前请求加入队列,等待新Token返回后重试 return new Observable(observer => { tokenService.enqueueRequest((newToken) => { const retryReq = req.clone({ setHeaders: { Authorization: `Bearer ${newToken}` } }); next(retryReq).subscribe({ next: res => observer.next(res), error: err => observer.error(err), complete: () => observer.complete() }); }); }); } // 未在刷新,发起Token续期请求 tokenService.setIsRefreshing(true); return tokenService.refreshAccessToken().pipe( switchMap(newToken => { // 处理队列中所有待请求 tokenService.processQueue(newToken); // 重新发送当前请求 const retryReq = req.clone({ setHeaders: { Authorization: `Bearer ${newToken}` } }); return next(retryReq); }), catchError(err => { // 续期失败,可在此处跳转登录页或处理全局错误 return throwError(() => err); }) ); };
关键细节说明
- 单例服务的作用:确保
isRefreshing状态和请求队列全局唯一,避免多个拦截器实例导致的状态冲突。 - 队列回调设计:每个队列项是接收新Token的函数,用于重新发起原请求,保证所有待处理请求都使用最新Token。
- 错误边界处理:Token续期失败后必须重置状态并清空队列,防止后续请求一直处于阻塞等待状态。
内容的提问来源于stack exchange,提问作者KurtGL
相关产品推荐
相关产品推荐

