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

Angular拦截器技术问题:处理源请求前通过Observable刷新Token

Angular拦截器中提前刷新Token并串联请求的实现方案

需求背景

在Angular应用中,为避免向后端发送大量携带无效Token的请求,需要在每个请求发起前主动检查Token有效性:

  • Token有效:直接转发请求
  • Token无效:先调用刷新Token的后端接口(返回Observable),刷新成功后再发送原请求
  • 要求提前主动刷新,而非等请求失败后再处理;同时保证同一时刻只有一个刷新请求,后续请求等待刷新完成后复用新Token

用户尝试实现时遇到Observable串联的类型匹配问题,现有代码如下:

export class AuthHeaderInterceptor implements HttpInterceptor {

    constructor() {}

    static authenticationOngoing = false;
    static authenticationObservable: Observable<boolean>;

    public intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

        /**
         * check if the token is valid.
         * If valid send request
         * If invalid call "refreshAuthentication()", wait for result, and send request based on result...
         */
        if (!this._isAuthenticationRequest(request) && !this.checkToken()) {
            if (!AuthHeaderInterceptor.authenticationOngoing) {
                // send request and wait for response
                AuthHeaderInterceptor.authenticationOngoing = true;
                AuthHeaderInterceptor.authenticationObservable = this.refreshAuthentication();
                AuthHeaderInterceptor.authenticationObservable.subscribe(() => {
                    AuthHeaderInterceptor.authenticationOngoing = false;
                    // submit source request or reroute if invalid
                });
            } else {
                AuthHeaderInterceptor.authenticationObservable.subscribe(() => {
                    // submit source request or reroute if invalid
                });
            }
        }
        return next.handle(request);
    }

    private checkToken() {
        return this._configStore.getToken().getEndtime() > new Date().getTime() + 10000;
    }

    private _isAuthenticationRequest(request): boolean {
        return request.url.endsWith('/api/authentication');
    }

    private refreshAuthentication(): Observable<boolean> {
        // returns Observable (as it's doing another request). Value represents if its successfully.
    }
}

修正后的实现方案

核心是利用RxJS的switchMap、shareReplay等操作符处理Observable串联,同时通过静态变量控制并发刷新请求,解决类型匹配问题:

import { Injectable } from '@angular/core';
import {
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpInterceptor,
  Observable
} from '@angular/common/http';
import { switchMap, catchError, shareReplay, finalize } from 'rxjs/operators';
import { of, throwError } from 'rxjs';

@Injectable()
export class AuthHeaderInterceptor implements HttpInterceptor {

  // 静态变量存储当前正在进行的刷新请求,避免重复发起
  static ongoingRefresh$: Observable<boolean> | null = null;

  constructor() {}

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 跳过刷新Token的请求本身,避免循环拦截
    if (this._isAuthenticationRequest(request)) {
      return next.handle(request);
    }

    // Token有效,直接添加认证头后转发请求
    if (this.checkToken()) {
      return next.handle(this._addAuthHeader(request));
    }

    // 无正在进行的刷新请求,发起新的刷新请求
    if (!AuthHeaderInterceptor.ongoingRefresh$) {
      AuthHeaderInterceptor.ongoingRefresh$ = this.refreshAuthentication().pipe(
        // 让多个订阅者共享同一刷新结果,避免重复处理
        shareReplay(1),
        // 刷新请求完成(成功/失败)后清空静态变量
        finalize(() => {
          AuthHeaderInterceptor.ongoingRefresh$ = null;
        })
      );
    }

    // 等待刷新完成后,根据结果处理原请求
    return AuthHeaderInterceptor.ongoingRefresh$.pipe(
      switchMap((refreshSuccess) => {
        if (refreshSuccess) {
          // 刷新成功,用新Token克隆并发送原请求
          return next.handle(this._addAuthHeader(request));
        } else {
          // 刷新失败,这里可跳转到登录页或抛出错误
          return throwError(() => new Error('Token刷新失败'));
        }
      }),
      catchError((error) => {
        // 刷新请求本身出错,清空静态变量并抛出错误
        AuthHeaderInterceptor.ongoingRefresh$ = null;
        return throwError(() => error);
      })
    );
  }

  private checkToken(): boolean {
    // 检查Token有效期,预留10秒缓冲时间
    const token = this._configStore.getToken();
    return token?.getEndtime() > new Date().getTime() + 10000;
  }

  private _isAuthenticationRequest(request: HttpRequest<any>): boolean {
    return request.url.endsWith('/api/authentication');
  }

  private refreshAuthentication(): Observable<boolean> {
    // 替换为实际的刷新Token请求逻辑,返回Observable<boolean>
    // 示例:
    // return this.authService.refreshToken().pipe(
    //   map(() => true),
    //   catchError(() => of(false))
    // );
    return of(true); // 模拟刷新成功,实际项目替换为真实请求
  }

  private _addAuthHeader(request: HttpRequest<any>): HttpRequest<any> {
    // 从store获取新Token,添加到请求头
    const token = this._configStore.getToken()?.getAccessToken();
    return request.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });
  }
}

关键实现说明

  • 并发控制:通过静态变量ongoingRefresh$记录当前刷新请求,避免同一时刻发起多个刷新请求
  • Observable串联:用switchMap将刷新请求与原请求串联,确保等待刷新完成后再发送原请求
  • 结果共享:shareReplay(1)让多个等待的请求共享同一刷新结果,避免重复处理
  • 请求克隆:刷新成功后克隆原请求并添加新认证头,保证请求携带有效Token
  • 错误处理:通过catchError和finalize处理刷新失败场景,及时清空静态变量避免影响后续请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 11:21:03