Angular HttpInterceptor中MSAL静默刷新令牌后失败请求未重发求助
Angular MSAL 401自动刷新令牌并重发请求解决方案
你的代码核心问题出在拦截器的错误处理逻辑没有接入rxjs响应流,以及部分分支未合法返回Observable,以下是具体修改方案:
问题根因
intercept方法中使用tap处理错误,tap仅用于执行副作用,无法将你返回的重发请求流替换到原执行链中,导致handleResponseError返回的Observable从未被订阅,自然不会触发重发。handleResponseError中401二次报错的分支没有返回Observable,不符合rxjs的catchError要求。- 排队等待刷新的逻辑未做内存泄漏防护。
修改后完整代码
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
相关产品推荐
相关产品推荐

