Angular拦截器引发HTTP请求大量循环问题求助
Angular拦截器Token刷新时出现大量请求循环问题排查与解决
我实现了一个Angular拦截器,预期在Access Token过期时自动刷新令牌,并用新令牌继续执行原请求。但现在遇到的问题是:当Token过期时,会触发大量重复请求循环(不是无限循环,执行100+次后停止),Token未过期时则无此问题,无法定位原因。
拦截器代码
let auth = inject(AuthService); if (!auth.AccessExpired()) return next(req); return auth.refresh().pipe( catchError(() => next(req)), switchMap((res) => { var response = res as RefreshResponse; const newReq = req.clone({ headers: req.headers.set("Authorization", `Bearer ${response.access_token}`) }) return next(newReq); }) )
刷新函数代码
refresh() { return this.http.post<RefreshResponse>(this.baseUrl + "refresh", {}, { headers: new HttpHeaders().set("refresh_token", this.refreshToken ?? "") }).pipe( tap( res => { console.log('responded' + res); this.accessToken = res.access_token } ), retry(2), catchError((err) => { console.log("errored", + err) this.logout(); this.noti.showError("Your session expired, log in again.", 5000); return throwError(() => new Error(err)); }) ) }
问题原因分析
- 刷新请求被拦截器重复捕获:刷新令牌的
refresh请求本身也会经过拦截器,此时Access Token已经过期,拦截器会再次触发刷新逻辑,形成循环。 - 操作符顺序错误:
catchError放在switchMap之前,一旦刷新请求失败,会直接放行原请求,但原请求的Token仍然过期,会再次进入拦截器引发循环。 - 重试机制放大循环次数:刷新函数中的
retry(2)会让失败的刷新请求重试2次,叠加拦截器的循环逻辑,导致请求次数大幅增加。
解决方案
1. 排除刷新请求不被拦截
在拦截器开头判断请求URL,跳过刷新接口的拦截处理:
let auth = inject(AuthService); // 跳过刷新请求,避免被拦截器重复处理 if (req.url.includes('refresh')) { return next(req); } if (!auth.AccessExpired()) return next(req); return auth.refresh().pipe( switchMap((res) => { const response = res as RefreshResponse; const newReq = req.clone({ headers: req.headers.set("Authorization", `Bearer ${response.access_token}`) }) return next(newReq); }), catchError(() => { // 刷新失败后可根据业务处理,比如跳转到登录页,这里暂时放行原请求 return next(req); }) )
2. 调整操作符顺序
将catchError移到switchMap之后,确保只有在刷新请求失败时才执行错误逻辑,避免提前放行过期Token的请求。
3. 控制刷新请求并发(可选)
添加状态标志,确保同一时间只有一个刷新请求在执行,避免多请求触发多次刷新:
private isRefreshing = false; private refreshSubject = new Subject<any>(); refresh() { if (this.isRefreshing) { return this.refreshSubject.asObservable(); } this.isRefreshing = true; return this.http.post<RefreshResponse>(this.baseUrl + "refresh", {}, { headers: new HttpHeaders().set("refresh_token", this.refreshToken ?? "") }).pipe( tap(res => { console.log('responded', res); this.accessToken = res.access_token; this.isRefreshing = false; this.refreshSubject.next(res); this.refreshSubject.complete(); }), retry(2), catchError((err) => { console.log("errored", err); this.isRefreshing = false; this.refreshSubject.error(err); this.refreshSubject.complete(); this.logout(); this.noti.showError("Your session expired, log in again.", 5000); return throwError(() => new Error(err)); }) ) }
内容的提问来源于stack exchange,提问作者Symtax
相关产品推荐
相关产品推荐

