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

Angular拦截器令牌使用及自动刷新功能实现问题求助

Angular令牌刷新拦截器修复方案

原代码核心问题

  • 在switchMap中嵌套subscribe,破坏RxJS流结构,导致拦截器无法正确返回Observable,后续逻辑失效
  • 刷新时机错误:请求完成后才判断令牌是否过期,应在发起请求前检查令牌是否即将过期并提前刷新
  • 未处理并发请求:多请求同时触发时会重复调用刷新接口
  • 令牌刷新后未正确复用原请求,逻辑不符合拦截器重试机制

修复后的拦截器代码

import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, of } from 'rxjs';
import { SapToken } from './sapToken.service';
import { switchMap, filter, take, catchError } from 'rxjs/operators';
import { ISapToken } from './model';

@Injectable()
export class SapTokenInterceptor implements HttpInterceptor {
  // 标记是否正在刷新令牌,避免并发请求重复触发刷新
  private isRefreshing = false;
  private refreshTokenSubject: BehaviorSubject<ISapToken | null> = new BehaviorSubject<ISapToken | null>(null);

  constructor(private _sapToken: SapToken) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // 跳过刷新令牌接口本身,防止循环拦截
    if (req.url.includes('/refresh-token')) {
      return next.handle(req);
    }

    let authReq = req;
    const currentToken = this._sapToken.sapToken.value;

    // 检查令牌是否存在,且距离过期不足30分钟时触发刷新
    if (currentToken) {
      const timeUntilExpire = this._sapToken.expireDuration(currentToken.expires);
      if (timeUntilExpire <= 30 * 60 * 1000) {
        return this.refreshTokenAndRetry(req, next);
      }
      // 令牌有效,直接添加请求头
      authReq = this.addTokenToRequest(req, currentToken.accessToken);
    }

    return next.handle(authReq);
  }

  private refreshTokenAndRetry(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (!this.isRefreshing) {
      this.isRefreshing = true;
      this.refreshTokenSubject.next(null);

      // 调用刷新令牌接口
      return this._sapToken.refreshSapSessionToken().pipe(
        switchMap((refreshedToken: ISapToken) => {
          this.isRefreshing = false;
          this.refreshTokenSubject.next(refreshedToken);
          this._sapToken.sapToken.next(refreshedToken);
          // 用新令牌重试原请求
          return next.handle(this.addTokenToRequest(req, refreshedToken.accessToken));
        }),
        catchError((err) => {
          this.isRefreshing = false;
          // 刷新失败可在此添加登出等降级逻辑
          return next.handle(req);
        })
      );
    } else {
      // 已有刷新请求在处理,等待刷新完成后用新令牌重试
      return this.refreshTokenSubject.pipe(
        filter(token => token !== null),
        take(1),
        switchMap(token => {
          return next.handle(this.addTokenToRequest(req, token!.accessToken));
        })
      );
    }
  }

  // 封装添加令牌到请求头的通用方法
  private addTokenToRequest(req: HttpRequest<any>, token: string): HttpRequest<any> {
    return req.clone({
      headers: req.headers.set('X-SAPAccessToken', token)
    });
  }
}

关键修复说明

  • 消除嵌套订阅:用RxJS操作符处理异步流,保证Observable的完整性
  • 提前触发刷新:发起请求前检查令牌有效期,提前30分钟刷新,避免请求时令牌已过期
  • 并发请求控制:通过isRefreshing标记和refreshTokenSubject,确保同一时间仅一个刷新请求,其他请求等待复用新令牌
  • 避免循环拦截:跳过刷新令牌接口的拦截逻辑
  • 复用原请求:刷新完成后用新令牌重新发起原请求,符合拦截器的重试逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 20:47:09