如何避免多个API请求因访问令牌过期返回401时重复调用刷新令牌接口
如何避免多个API请求因访问令牌过期返回401时重复调用刷新令牌接口
我完全理解你的困扰——当多个API请求同时因为access token过期返回401时,每个请求都会单独触发一次refresh token接口调用,最后只有一次成功,其他全是冗余请求,既浪费资源又不够高效。咱们可以通过让刷新令牌请求成为共享的单例Observable来解决这个问题,确保同一时间只会发起一次refresh请求,所有触发401的请求都复用这次请求的结果。
核心思路
问题根源在于当前的错误拦截器里,每个401请求都会独立调用refresh()方法发起新的HTTP请求。我们需要在AuthService里做一层封装:当有第一个请求触发刷新时,保存这个请求的Observable,后续的401请求直接订阅这个已有的Observable,直到刷新请求完成(成功或失败)后再重置状态。
具体代码修改
第一步:改造AuthService,实现共享的刷新请求
@Injectable({ providedIn: 'root' }) export class AuthService { // 用来缓存当前正在进行的refresh请求,避免重复调用 private refreshTokenRequest: Observable<any> | null = null; constructor(private http: HttpClient, private storage: StorageService) {} refresh(): Observable<any> { // 如果已经有正在进行的refresh请求,直接返回这个请求的Observable if (this.refreshTokenRequest) { return this.refreshTokenRequest; } // 没有正在进行的请求,发起新的refresh请求 const storedTokens = this.getTokens(); this.refreshTokenRequest = this.http.post('/auth/refresh', {}, { headers: new HttpHeaders().set('Authorization', storedTokens.refresh_token) }).pipe( tap((newTokens) => { // 保存新获取的tokens到存储 this.saveTokens(newTokens); }), finalize(() => { // 请求完成(成功/失败)后,清空缓存的请求引用 this.refreshTokenRequest = null; }) ); return this.refreshTokenRequest; } // 你的其他已有方法:getTokens、saveTokens、logout等 }
第二步:修改ErrorInterceptor(逻辑基本不变,复用改造后的refresh方法)
@Injectable() export class ErrorInterceptor implements HttpInterceptor { constructor(private router: Router, private authService: AuthService) { } intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { return next.handle(request).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401) { // 现在调用的refresh()会返回共享的请求,不会重复发起 return this.authService.refresh().pipe( switchMap(() => { const storedTokens = this.authService.getTokens(); const updatedRequest = request.clone({ headers: request.headers.set("Authorization", storedTokens.access_token) }); return next.handle(updatedRequest); }), catchError((refreshError: any) => { // 刷新失败,说明refresh token也失效了,直接登出 this.authService.logout(); return throwError(refreshError); }) ); } else if (error.status === 403) { this.authService.logout(); } return throwError(() => new Error(error.message)); }) ); } }
原理说明
- 当第一个401请求触发
refresh()时,refreshTokenRequest会被赋值为当前的HTTP请求Observable,后续的401请求进来时,会直接返回这个已有的Observable,不会再发起新的refresh请求。 - 当refresh请求完成(不管成功还是失败),
finalize操作符会把refreshTokenRequest重置为null,这样下次需要刷新时可以正常发起新的请求。 - 你的AuthInterceptor逻辑已经处理了refresh请求的token携带(判断url包含
/auth/refresh时用refresh_token),这部分不需要修改。
备注:内容来源于stack exchange,提问作者Abhishek Murali
相关产品推荐
相关产品推荐

