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

Angular HttpInterceptor中MSAL静默刷新令牌后失败请求未重发求助

Angular MSAL 401自动刷新令牌并重发请求解决方案

你的代码核心问题出在拦截器的错误处理逻辑没有接入rxjs响应流,以及部分分支未合法返回Observable,以下是具体修改方案:


问题根因

  1. intercept方法中使用tap处理错误,tap仅用于执行副作用,无法将你返回的重发请求流替换到原执行链中,导致handleResponseError返回的Observable从未被订阅,自然不会触发重发。
  2. handleResponseError中401二次报错的分支没有返回Observable,不符合rxjs的catchError要求。
  3. 排队等待刷新的逻辑未做内存泄漏防护。

修改后完整代码

import { catchError, switchMap, tap, take, EMPTY, Observable, HttpEvent, HttpRequest, HttpHandler, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Router } from '@angular/router';
import { MsalService } from '@azure/msal-angular';
import { BehaviorSubject } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  private refreshTokenInProgress = false;
  private tokenRefreshedSource = new BehaviorSubject<void>(null);
  private tokenRefreshed$ = this.tokenRefreshedSource.asObservable();

  constructor(
    private sessionService: any, // 替换为你实际的服务类型
    private msalAuthService: MsalService,
    private authServices: any, // 替换为你实际的服务类型
    private authentiCationService: any, // 替换为你实际的服务类型
    private router: Router
  ) {}

  addAuthHeader(request: HttpRequest<any>) {
    const tokenType = "Bearer";
    const authHeader = this.sessionService.getAccessToken();
    if (authHeader) {
      return request.clone({
        setHeaders: {
          "Authorization": `${tokenType} ${authHeader}`
        }
      });
    }
    return request;
  }

  refreshToken(): Observable<any> {
    if (this.refreshTokenInProgress) {
      // 增加take(1)避免内存泄漏,收到刷新完成通知后自动取消订阅
      return this.tokenRefreshed$.pipe(take(1));
    } else {
      this.refreshTokenInProgress = true;
      const loginRequest = {
        scopes: ['openId','profile'],
      };

      return this.msalAuthService.acquireTokenSilent(loginRequest).pipe(
        tap((payloadInfo) => {
          this.authServices.setSessions(payloadInfo); 
          this.refreshTokenInProgress = false;
          this.tokenRefreshedSource.next();
        }),
        catchError(() => {
          this.refreshTokenInProgress = false;
          this.authServices.logout();
          return EMPTY;
        })
      )
    }
  }

  handleResponseError(error: any, request?: HttpRequest<any>, next?: HttpHandler) {
    if (error.status === 401 && request && next) {
      return this.refreshToken().pipe(
        switchMap(() => {
          const newRequest = this.addAuthHeader(request);
          return next.handle(newRequest);
        }),
        catchError(e => {
          if (e.status !== 401) {
            return this.handleResponseError(e);
          } else {
            this.authentiCationService.logout();
            this.router.navigate(["logout"], { replaceUrl: true });
            // 分支必须返回Observable
            return EMPTY;
          }
        })
      );
    }
    // 非401错误直接抛出
    throw error;
  }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let processedReq = req.clone({ 
      headers: req.headers
        .set("Accept", "*/*")
        .set("Content-Type", "application/json")
    });
    processedReq = this.addAuthHeader(processedReq);

    return next.handle(processedReq).pipe(
      tap((event: HttpEvent<any>) => {
        if (event instanceof HttpResponse) {
          // 原有成功逻辑保留
        }
      }),
      // 核心修改:将tap的错误回调替换为catchError,接入响应流
      catchError((err: any) => {
        return this.handleResponseError(err, processedReq, next);
      })
    );
  }
}

关键修改说明

  • 把拦截器中tap的错误处理逻辑替换为catchError,将刷新令牌+重发请求的流返回给rxjs执行,才能触发后续重发逻辑。
  • 给所有catchError的分支补全了Observable返回值,避免rxjs运行时报错。
  • 优化了排队刷新令牌的逻辑,用take(1)避免内存泄漏。
  • 合并了重复的请求clone操作,减少不必要的性能开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 23:54:03