如何避免Access Token过期时重复调用Refresh Token并重发401请求
我基于拦截器实现了Refresh Token逻辑,功能正常,但存在一个关键问题:当多个API并行调用且全部返回401时,Refresh Token会被多次触发调用,次数取决于this.isRefreshing变量设为true的速度。需要实现仅调用一次Refresh Token API,让所有返回401的待处理API在Token刷新后重新发起请求。

截图中3个API均返回401,当前逻辑会多次调用Refresh Token,期望仅调用一次,其余请求在Token刷新后自动重试。
修改后的拦截器代码
import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from "@angular/common/http"; import { Injectable, Injector } from "@angular/core"; import { BehaviorSubject, Observable, throwError } from "rxjs"; import { catchError, filter, switchMap, take } from "rxjs/operators"; import { AuthenticationService } from "../services"; import { TokenService } from "../services/token.service"; @Injectable() export class AuthInterceptor implements HttpInterceptor { private isRefreshing = false; private tokenService: TokenService; private refreshTokenSubject: BehaviorSubject<any> = new BehaviorSubject<any>(null); constructor(private injector: Injector) { this.tokenService = this.injector.get(TokenService); } intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // 给请求添加有效Token const token = this.tokenService.accessToken; if (token) { req = this.addTokenHeader(req, token); } // 携带credentials req = req.clone({ withCredentials: true }); // 排除Refresh Token接口本身,避免拦截循环 if (req.url.includes('refreshToken')) { // 替换为你的实际Refresh Token接口路径 return next.handle(req); } return next.handle(req).pipe(catchError(error => { if (error instanceof HttpErrorResponse && error.status === 401) { return this.handle401Error(req, next); } return throwError(error); })); } private handle401Error(request: HttpRequest<any>, next: HttpHandler) { const authService = this.injector.get(AuthenticationService); if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); return authService.refreshToken().pipe( switchMap((token: any) => { this.isRefreshing = false; this.tokenService.accessToken = token.accessToken; this.refreshTokenSubject.next(token.accessToken); // 用新Token重新发起当前请求 return next.handle(this.addTokenHeader(request, token.accessToken)); }), catchError((err) => { this.isRefreshing = false; this.refreshTokenSubject.next(null); authService.directRedirectToLogin(); return throwError(err); }) ); } // 等待Refresh Token完成,拿到新Token后重新发起请求 return this.refreshTokenSubject.pipe( filter(token => token !== null), take(1), switchMap(token => next.handle(this.addTokenHeader(request, token))) ); } private addTokenHeader(request: HttpRequest<any>, token: string) { const setHeaders: any = { Authorization: `Bearer ${token}` }; // 非FormData请求添加Content-Type if (!(request.body instanceof FormData)) { setHeaders['Content-Type'] = 'application/json'; } return request.clone({ setHeaders }); } }
关键改动说明
排除Refresh Token请求拦截
在intercept方法中添加判断,跳过对Refresh Token接口的拦截,避免因该接口返回401导致的无限循环调用。确保所有请求使用新Token重试
无论是第一个触发Refresh Token的请求,还是等待的并行请求,都通过addTokenHeader方法用新Token克隆请求后再发起,避免使用未携带新Token的原请求。优化状态管理
在Refresh Token请求失败时,重置refreshTokenSubject为null,避免后续请求一直等待无效状态。恢复初始Token添加逻辑
正常请求发起时自动携带当前有效Token,减少不必要的401触发场景。
实现原理
当多个并行请求同时返回401时,第一个请求会将isRefreshing设为true并发起Refresh Token调用;后续所有请求会进入等待分支,监听refreshTokenSubject的状态变化。当Refresh Token成功获取新Token后,refreshTokenSubject会发出新Token值,所有等待的请求都会用新Token重新发起,从而实现仅调用一次Refresh Token,所有请求自动重试的效果。
内容的提问来源于stack exchange,提问作者Sumit patel

