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.')); } }) ); };
修改关键点说明
- 状态全局共享:将
refreshTokenSubject和isRefreshing移到单例服务中,确保所有请求共用同一个状态实例,解决了本地变量无法跨请求传递更新的问题。 - 修复状态重置逻辑:移除拦截器开头的
isRefreshing = false,避免每次请求错误重置刷新状态。 - 请求队列化处理:当
isRefreshing为true时,不再丢弃请求,而是订阅全局Subject等待token刷新完成,自动重新发起请求。
内容的提问来源于stack exchange,提问作者alegeomx
相关产品推荐
相关产品推荐

