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

Angular 17拦截器本地BehaviorSubject无法正常工作问题

Angular 17函数式拦截器中BehaviorSubject失效的解决方法

问题核心原因

你的拦截器逻辑失效的关键问题在于:refreshTokenSubject是拦截器函数内的本地变量。Angular的函数式拦截器每次处理请求时都会重新执行整个函数,这意味着每个请求都会创建一个全新的refreshTokenSubject实例。当第一个请求触发token刷新并调用next时,后续请求的Subject完全是另一个独立对象,根本接收不到更新值,自然无法激活过滤器处理队列请求。

此外还有两处逻辑漏洞:

  • 拦截器开头直接重置refreshTokenManageService.isRefreshing = false,会导致并发请求时刷新状态被错误重置,逻辑混乱。
  • 当前逻辑里,当isRefreshing为true时直接返回EMPTY,会丢弃后续请求,而非将它们加入队列等待token刷新完成。

解决方案:将状态移到单例服务中

把BehaviorSubject和刷新标志移到RefreshTokenManageService单例服务中,让所有请求共享同一个状态实例。

第一步:修改RefreshTokenManageService

@Injectable({ providedIn: 'root' })
export class RefreshTokenManageService {
  // 全局共享的token更新通知Subject
  public refreshTokenSubject = new BehaviorSubject<RefreshTokenResponse>({ accessToken: '', refreshToken: '' });
  // 全局刷新状态标志
  public isRefreshing = false;

  // 保留你原有方法的实现
  addTokenHeader(request: HttpRequest<any>) {
    // 你的原有逻辑
  }

  addContentTypeHeader(request: HttpRequest<any>) {
    // 你的原有逻辑
  }

  getUsuarioDataStorage() {
    // 你的原有逻辑
  }

  updateTokens(accessToken: string, refreshToken: string) {
    // 你的原有逻辑
  }
}

第二步:修改拦截器代码

export const RequestHeadersInterceptor: HttpInterceptorFn = (request, next) => {
  const refreshTokenManageService = inject(RefreshTokenManageService);
  const authService = inject(AuthService);
  const router = inject(Router);

  // 登录请求直接放行
  if (request.url === URL_AUTH_LOGIN) {
    return next(request);
  }

  // 刷新token的请求,直接添加现有refreshToken头后放行
  if (request.url === URL_AUTH_REFRESH_TOKEN) {
    const requestClone = refreshTokenManageService.addTokenHeader(request);
    return next(requestClone);
  }

  const usuarioDataStorage = refreshTokenManageService.getUsuarioDataStorage();
  if (!usuarioDataStorage || !usuarioDataStorage.accessToken) {
    router.navigateByUrl('/').then();
    return EMPTY;
  }

  // 给请求添加token和content-type头
  const requestAuthClone = refreshTokenManageService.addTokenHeader(request);
  const requestContentTypeClone = refreshTokenManageService.addContentTypeHeader(requestAuthClone);

  return next(requestContentTypeClone).pipe(
    catchError((requestError: HttpErrorResponse) => {
      if (requestError.status === HttpStatusCode.Unauthorized) {
        // 未在刷新token,触发刷新流程
        if (!refreshTokenManageService.isRefreshing) {
          refreshTokenManageService.isRefreshing = true;
          // 先发射空值,避免旧token干扰
          refreshTokenManageService.refreshTokenSubject.next({ accessToken: '', refreshToken: '' });

          return authService.refreshToken().pipe(
            switchMap((refreshTokenResponse) => {
              refreshTokenManageService.isRefreshing = false;
              // 更新全局Subject,通知所有等待的请求
              refreshTokenManageService.refreshTokenSubject.next(refreshTokenResponse);
              refreshTokenManageService.updateTokens(refreshTokenResponse.accessToken, refreshTokenResponse.refreshToken);

              // 重新发起当前请求
              const requestBearerClone = refreshTokenManageService.addTokenHeader(request);
              const requestContentTypeClone = refreshTokenManageService.addContentTypeHeader(requestBearerClone);
              return next(requestContentTypeClone);
            }),
            catchError((error) => {
              refreshTokenManageService.isRefreshing = false;
              const authenticationError = new AuthenticationError(error.error.message);
              router.navigateByUrl('/').then();
              return throwError(() => authenticationError);
            })
          );
        } else {
          // 正在刷新token,等待全局Subject更新后重新发起请求
          return refreshTokenManageService.refreshTokenSubject.pipe(
            filter(result => result.accessToken !== ''),
            take(1),
            switchMap(() => {
              const requestBearerClone = refreshTokenManageService.addTokenHeader(request);
              const requestContentTypeClone = refreshTokenManageService.addContentTypeHeader(requestBearerClone);
              return next(requestContentTypeClone);
            })
          );
        }
      } else {
        return throwError(() => new ServerError('Por el momento el servicio no se encuentra disponible. Favor de intentar más tarde.'));
      }
    })
  );
};

修改关键点说明

  1. 状态全局共享:将refreshTokenSubject和isRefreshing移到单例服务中,确保所有请求共用同一个状态实例,解决了本地变量无法跨请求传递更新的问题。
  2. 修复状态重置逻辑:移除拦截器开头的isRefreshing = false,避免每次请求错误重置刷新状态。
  3. 请求队列化处理:当isRefreshing为true时,不再丢弃请求,而是订阅全局Subject等待token刷新完成,自动重新发起请求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 07:34:54