Angular错误拦截器多401响应问题:并发请求刷新令牌方案咨询
Angular并发请求下Refresh Token重复触发问题解决方案
问题场景
Access Token过期后刷新页面,同时发起的3个授权请求全部返回401错误。拦截器本应调用刷新接口获取新Token,但因为并发请求的竞态问题:第一个请求刷新Token后更新了本地存储,后续两个请求仍携带旧Token触发401拦截,导致重复执行刷新逻辑。
核心问题
当前拦截器未处理并发请求下的Token刷新冲突,多个请求同时触发401时会重复调用刷新接口,且后续请求无法复用已刷新的新Token。
解决方案思路
- 用一个共享Observable标记是否正在刷新Token,避免重复调用刷新接口
- 所有触发401的请求订阅这个共享Observable,等Token刷新完成后,统一用新Token重新发起请求
- 刷新失败时统一执行登出逻辑
修改后的ErrorInterceptor代码
@Injectable({ providedIn: 'root' }) export class ErrorInterceptor implements HttpInterceptor { // 共享的刷新Token请求Observable,控制并发请求 private refreshTokenRequest$: Observable<IUser | null> | null = null; constructor(private readonly authService: AuthService) {} intercept( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { return next.handle(req).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401 && !req.url.includes('auth/login')) { return this.handle401HttpError(req, next); } // 非401错误直接抛出 return throwError(() => error); }) ); } private handle401HttpError( req: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { // 已有刷新请求在进行,直接订阅等待结果,避免重复刷新 if (this.refreshTokenRequest$) { return this.refreshTokenRequest$.pipe( switchMap(user => { if (user) { return next.handle(this.addAuthHeader(req, user.token)); } return throwError(() => new Error('刷新Token失败')); }), catchError(() => { this.authService.logout(); return throwError(() => new Error('刷新Token失败,已登出')); }) ); } // 无正在进行的刷新请求,发起新的刷新请求 const currentAccessToken = localStorage.getItem('token') as string; const currentRefreshToken = localStorage.getItem('refreshToken') as string; const request: IRefreshTokenRequest = { accessToken: currentAccessToken, refreshToken: currentRefreshToken, }; // 保存刷新请求Observable,用finalize确保请求结束后清空标记 this.refreshTokenRequest$ = this.authService.refreshToken(request).pipe( finalize(() => { this.refreshTokenRequest$ = null; }) ); return this.refreshTokenRequest$.pipe( switchMap(user => { if (user) { localStorage.setItem('token', user.token); localStorage.setItem('refreshToken', user.refreshToken); return next.handle(this.addAuthHeader(req, user.token)); } return throwError(() => new Error('刷新Token失败')); }), catchError(err => { this.authService.logout(); return throwError(() => err); }) ); } // 辅助方法:为请求添加新的Authorization头 private addAuthHeader(req: HttpRequest<any>, token: string): HttpRequest<any> { return req.clone({ setHeaders: { Authorization: `Bearer ${token}`, }, }); } }
关键改动说明
- 新增
refreshTokenRequest$变量标记刷新状态,所有并发401请求共享该Observable,避免重复调用刷新接口 - 用
finalize操作符在刷新请求结束(成功/失败)后清空标记,确保后续需要时能发起新的刷新 - 移除原代码中错误的
.subscribe()调用,改为返回Observable让HTTP客户端处理订阅,避免丢失请求上下文 - 提取
addAuthHeader辅助方法,简化代码逻辑
内容的提问来源于stack exchange,提问作者AntoBoy
相关产品推荐
相关产品推荐

