You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular拦截器处理401令牌失效:多请求重复刷新且未用新令牌

问题解决:Angular拦截器中避免重复刷新令牌并正确重试请求

现有代码的问题

  • 触发401/403的每个请求都会独立发起刷新令牌请求,造成重复调用
  • 原请求没有使用刷新后的新令牌重新发起,直接抛出错误
  • async/await与RxJS Observable结合不当,拦截器未正确返回重试流

修正方案

首先在AuthenticationService中添加共享的刷新令牌Observable,防止并发请求:

@Injectable({ providedIn: 'root' })
export class AuthenticationService {
  private refreshToken$: Observable<any> | null = null;

  // 其他原有代码...

  refreshToken(): Observable<any> {
    // 已有刷新请求在进行时,直接返回现有流
    if (this.refreshToken$) {
      return this.refreshToken$;
    }

    // 发起刷新请求并维护流状态
    this.refreshToken$ = this.http.post('/api/refresh-token', {})
      .pipe(
        tap((response) => {
          // 更新本地存储的新令牌
          this.tokenValue = response.accessToken;
        }),
        finalize(() => {
          // 请求完成后清空刷新流标记
          this.refreshToken$ = null;
        }),
        catchError((err) => {
          // 刷新失败时清空令牌,避免无限循环
          this.tokenValue = null;
          this.refreshToken$ = null;
          return throwError(() => err);
        })
      );

    return this.refreshToken$;
  }
}

然后修改ErrorInterceptor,正确处理令牌刷新与请求重试逻辑:

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  constructor(
    private authenticationService: AuthenticationService,
    private headerRequestService: HeaderRequestService
  ) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError((err) => {
        // 仅处理401/403且当前存在有效令牌的场景
        if ([401, 403].includes(err.status) && this.authenticationService.tokenValue) {
          // 刷新令牌后重试原请求
          return this.authenticationService.refreshToken().pipe(
            switchMap(() => {
              // 克隆原请求并添加新令牌头
              const newRequest = this.headerRequestService.addHeader(request);
              return next.handle(newRequest);
            }),
            catchError((refreshErr) => {
              // 刷新失败时抛出最终错误
              const error = (refreshErr?.error?.message) || refreshErr.statusText;
              console.error(refreshErr);
              return throwError(() => error);
            })
          );
        }

        // 非令牌过期类错误直接抛出
        const error = (err?.error?.message) || err.statusText;
        console.error(err);
        return throwError(() => error);
      })
    );
  }
}

关键改进点

  • 避免重复刷新:通过共享的refreshToken$确保同一时间仅发起一个刷新请求
  • 正确重试请求:用switchMap衔接刷新流与原请求重试逻辑,确保使用新令牌
  • 完善错误处理:刷新失败时清空令牌,避免无限触发刷新逻辑
  • 规范异步流处理:移除不合适的async/await,统一用RxJS操作符处理异步流程

内容的提问来源于stack exchange,提问作者kareem nagy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 17:10:26