Angular拦截器刷新令牌时请求重复3次问题求助
Angular Token刷新拦截器重复请求问题解决
问题描述
我实现了一个用于刷新Access Token的Angular拦截器,无需刷新令牌时一切正常;但触发刷新操作时,恰好经过该拦截器的请求会重复执行3次,同时刷新请求本身也会运行3次。我已经移除了refresh方法里的retry(2),但问题还是没解决。
原拦截器代码
export const refreshInterceptor: HttpInterceptorFn = (req, next) => { let auth = inject(AuthService); if (!auth.loggedIn() || !auth.AccessExpired() || req.headers.has('refresh_token') || req.headers.has("no-intercept")) { 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方法代码
refresh() { if (this.loggedIn()) { return this.http.post<RefreshResponse>(this.baseUrl + "refresh", {}, { headers: new HttpHeaders().set("refresh_token", this.refreshToken ?? "") }).pipe( tap( res => { console.log('Responded ' + res.access_token); 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)); }) ) } return throwError(() => "User is not logged in, no refresh possible"); }
问题根源
核心问题是并发请求触发了多次刷新操作:当多个请求同时进入拦截器,且都检测到Token过期时,每个请求都会独立调用auth.refresh(),导致重复发起刷新API;同时每个刷新完成后,又会重新发起原请求,最终造成请求重复执行。
解决方案
在AuthService中维护一个共享的刷新请求Observable,确保同一时间只有一个刷新请求在执行,所有需要等待刷新的请求复用同一个刷新结果。
修改后的AuthService代码
private refreshRequest$: Observable<RefreshResponse> | null = null; refresh() { if (!this.loggedIn()) { return throwError(() => "用户未登录,无法刷新令牌"); } // 如果已有刷新请求在进行,直接复用这个请求流 if (this.refreshRequest$) { return this.refreshRequest$; } this.refreshRequest$ = this.http.post<RefreshResponse>(`${this.baseUrl}refresh`, {}, { headers: new HttpHeaders().set("refresh_token", this.refreshToken ?? "") }).pipe( tap(res => { console.log('刷新成功,新Token:' + res.access_token); this.accessToken = res.access_token; }), // 请求完成(成功/失败)后清空缓存的请求对象 finalize(() => { this.refreshRequest$ = null; }), catchError((err) => { console.log("刷新失败:" + err); this.logout(); this.noti.showError("会话已过期,请重新登录", 5000); return throwError(() => new Error(err)); }) ); return this.refreshRequest$; }
修改后的拦截器代码
export const refreshInterceptor: HttpInterceptorFn = (req, next) => { const auth = inject(AuthService); // 跳过拦截的场景:未登录、Token未过期、携带refresh_token头、携带no-intercept头 if (!auth.loggedIn() || !auth.AccessExpired() || req.headers.has('refresh_token') || req.headers.has("no-intercept")) { return next(req); } return auth.refresh().pipe( // 刷新成功后,用新Token克隆并发起请求 switchMap((res) => { const newReq = req.clone({ headers: req.headers.set("Authorization", `Bearer ${res.access_token}`) }); return next(newReq); }), // 刷新失败时,直接发起原请求(可根据业务调整逻辑) catchError(() => next(req)) ); };
关键修改点
- 添加刷新请求缓存:用
refreshRequest$变量缓存正在进行的刷新请求,多个并发请求进来时复用同一个Observable,避免重复调用刷新API。 - 自动清理缓存:通过
finalize操作符在请求完成后清空缓存,确保后续需要刷新时能正常发起新请求。 - 调整拦截器逻辑:将
catchError移到switchMap之后,逻辑更清晰,避免不必要的请求重复触发。
内容的提问来源于stack exchange,提问作者Symtax
相关产品推荐
相关产品推荐

