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

Angular 5 Http拦截器JWT令牌刷新:请求排队问题

解决Angular拦截器中并发401请求的令牌刷新信号量问题

这是个非常常见的场景——当多个请求同时触发401未授权时,我们绝对不想重复发起刷新令牌的请求,而是要让所有后续请求挂起,等待令牌刷新完成后,用新令牌重新发起这些请求。下面是修改后的TokenInterceptor实现,附带详细的逻辑说明:

import { Injectable } from '@angular/core';
import { HttpRequest, HttpHandler, HttpInterceptor, HttpEvent } from '@angular/common/http';
import { Observable, Subject, throwError } from 'rxjs';
import { catchError, switchMap, finalize } from 'rxjs/operators';
import { AuthService } from './auth.service'; // 请确保导入路径正确

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  private auth: AuthService;
  // 跟踪是否正在刷新令牌
  private refreshTokenInProgress = false;
  // 作为信号量,通知等待的请求刷新已完成
  private refreshTokenSubject: Subject<any> = new Subject<any>();

  constructor(private injector: Injector) { }

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    this.auth = this.injector.get(AuthService);
    
    // 为请求添加认证头(如果已登录)
    if(this.auth.isAuthenticated()){
      request = request.clone({
        setHeaders: {
          Accept: 'application/json',
          Authorization: `Bearer ${localStorage.getItem('access_token')}`
        }
      });
    } else {
      request = request.clone({
        setHeaders: {
          Accept: 'application/json'
        }
      });
    }

    return next.handle(request).pipe(
      catchError(error => {
        // 仅处理401未授权错误
        if (error.status === 401) {
          // 排除刷新令牌的请求本身,避免无限循环拦截
          if (!request.url.includes('/refresh-token')) {
            return this.handle401Error(request, next);
          }
        }
        // 其他错误直接抛出
        return throwError(() => error);
      })
    );
  }

  private handle401Error(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 如果没有正在刷新令牌,启动刷新流程
    if (!this.refreshTokenInProgress) {
      this.refreshTokenInProgress = true;
      // 重置信号量Subject,确保每次刷新都是新的通知
      this.refreshTokenSubject = new Subject<any>();

      // 调用AuthService的刷新令牌方法
      return this.auth.refreshToken().pipe(
        switchMap((newToken: any) => {
          // 刷新成功,更新本地存储的access_token
          localStorage.setItem('access_token', newToken.access_token);
          // 通知所有等待的请求:刷新完成
          this.refreshTokenSubject.next(newToken);
          // 重发当前请求(用新令牌)
          return next.handle(this.addAuthHeader(request));
        }),
        catchError((refreshError) => {
          // 刷新失败,清空认证状态,跳转到登录页
          this.auth.logout();
          this.refreshTokenSubject.error(refreshError);
          return throwError(() => refreshError);
        }),
        finalize(() => {
          // 无论成功失败,重置刷新状态
          this.refreshTokenInProgress = false;
          this.refreshTokenSubject.complete();
        })
      );
    } else {
      // 已有刷新请求在进行,让当前请求等待刷新完成后重试
      return this.refreshTokenSubject.asObservable().pipe(
        switchMap(() => {
          // 刷新完成,用新令牌重发请求
          return next.handle(this.addAuthHeader(request));
        })
      );
    }
  }

  // 封装添加认证头的方法,避免重复代码
  private addAuthHeader(request: HttpRequest<any>): HttpRequest<any> {
    return request.clone({
      setHeaders: {
        Accept: 'application/json',
        Authorization: `Bearer ${localStorage.getItem('access_token')}`
      }
    });
  }
}

关键逻辑说明:

  • 状态跟踪变量:

    • refreshTokenInProgress:标记当前是否正在执行令牌刷新操作,避免重复发起刷新请求
    • refreshTokenSubject:作为“信号量”,当刷新完成时,通过它通知所有挂起的请求可以重试了
  • 401错误处理分支:

    1. 首先排除刷新令牌的请求本身,防止拦截器无限循环处理这个请求的401错误
    2. 如果没有正在刷新,就启动刷新流程:调用auth.refreshToken(),成功后更新令牌、通知等待的请求,并重发当前请求;失败则清空认证状态并跳转登录
    3. 如果已经有刷新请求在进行,当前请求就订阅refreshTokenSubject,等待刷新完成后自动用新令牌重试
  • 代码优化:把添加认证头的逻辑封装成addAuthHeader方法,减少重复代码,提高可维护性

注意事项:

  1. 请确保你的AuthService中已经实现了refreshToken()方法,该方法应该返回一个Observable,发出新的令牌数据
  2. 如果你的Angular版本使用的是RxJS 6+,请确保使用pipe操作符来组合操作符(如catchError、switchMap),旧版本的catch已经被废弃
  3. 可以根据你的业务需求调整刷新失败后的处理逻辑(比如跳转登录页的方式)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:54:17