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

NestJS出站请求拦截器添加及Authorization头缺失问题排查

问题:NestJS拦截器无法为出站请求添加Authorization头

我正在开发一个作为API与其他服务中间件的NestJS应用,需求是给每个出站请求添加Bearer Token,若Token被拒绝则获取新Token并重试请求。我用拦截器添加Authorization头,但目标服务一直返回403 Forbidden,排查后发现请求实际没携带这个头。

简化版拦截器代码:

intercept(context: ExecutionContext, next: CallHandler) {
  const request = context.switchToHttp().getRequest<Request>();
  request.headers.Authorization = this.token;
  return next.handle();
}

这并非添加Authorization头的正确方式吗?

完整拦截器代码:

@Injectable()
export class AuthTokenInterceptor implements NestInterceptor {
  private readonly authUrl = "https://...";
  private token = "";

  constructor(private readonly http: HttpService) {
    // 尽快获取Token
    this.trySaveToken();
  }

  intercept(context: ExecutionContext, next: CallHandler) {
    const request = context.switchToHttp().getRequest<Request>();

    if (!request.headers.Authorization) {
      request.headers.Authorization = this.token;
    }

    return next.handle().pipe(
      catchError((error: AxiosError) => {
        const originalRequest = error.config;

        console.log(originalRequest?.headers); // 这里没有Authorization头!

        // 如果是认证错误,大概率需要重新获取Token
        if (error.response?.status === 401 && originalRequest != null && originalRequest.url !== this.authUrl) {
          // 不加第二个判断会无限循环
          this.trySaveToken();
          originalRequest.headers.Authorization = this.token; // 为当前请求覆盖旧Token
          return of(originalRequest); // 用新Token重试请求
        } else {
          return throwError(() => error);
        }
      }),
  }

  private trySaveToken() {
    this.http.post(this.authUrl).subscribe({
      next: (response) => {
        this.token = 'Bearer ' + response.data.access_token;
      },
    });
  }
}

我的重试逻辑可能还存在问题,但在解决Authorization头缺失问题前无法推进。

console.log输出内容:

Object [AxiosHeaders] {
  Accept: 'application/json, text/plain, */*',    
  'Content-Type': 'application/json',
  'User-Agent': 'axios/1.6.8',
  'Content-Length': '162',
  'Accept-Encoding': 'gzip, compress, deflate, br'
}

问题分析与解决方案

核心问题

  1. 拦截对象错误:你当前用的Nest拦截器是处理客户端发给你的入站请求,修改的是这个请求的headers,完全不会影响你的应用发给其他服务的出站请求。
  2. Token初始化异步未处理:构造函数里调用trySaveToken是异步请求,但没等它完成就发起业务请求,导致初始token是空字符串。
  3. 重试逻辑无效:return of(originalRequest)只是把请求配置对象返回,不会触发Axios重新发送请求。

正确实现方式

要处理出站请求,得用HttpService底层的Axios拦截器,而非Nest的Interceptor。下面是修正后的代码:

1. 重构拦截器(使用Axios拦截器)

@Injectable()
export class AuthTokenInterceptor implements OnModuleInit {
  private readonly authUrl = "https://...";
  private token = "";
  private isRefreshing = false;
  private refreshSubscribers: ((token: string) => void)[] = [];

  constructor(private readonly httpService: HttpService) {}

  async onModuleInit() {
    // 先初始化Token,确保后续请求有可用凭证
    await this.trySaveToken();
    
    // 给Axios添加请求拦截器,统一处理Authorization头
    this.httpService.axiosRef.interceptors.request.use(
      (config) => {
        if (this.token && !config.headers.Authorization) {
          config.headers.Authorization = this.token;
        }
        return config;
      },
      (error) => Promise.reject(error)
    );

    // 添加响应拦截器,处理Token过期重试
    this.httpService.axiosRef.interceptors.response.use(
      (response) => response,
      async (error: AxiosError) => {
        const originalRequest = error.config;
        
        // 判断是否需要刷新Token并重试
        if (
          error.response?.status === 401 &&
          originalRequest &&
          originalRequest.url !== this.authUrl &&
          !originalRequest._retry
        ) {
          // 防止多个请求同时触发Token刷新
          if (this.isRefreshing) {
            return new Promise((resolve) => {
              this.refreshSubscribers.push((token) => {
                originalRequest.headers.Authorization = token;
                resolve(this.httpService.axiosRef(originalRequest));
              });
            });
          }

          originalRequest._retry = true;
          this.isRefreshing = true;

          try {
            // 获取新Token
            await this.trySaveToken();
            originalRequest.headers.Authorization = this.token;
            
            // 通知所有等待的请求重试
            this.refreshSubscribers.forEach((callback) => callback(this.token));
            this.refreshSubscribers = [];
            
            // 重试原请求
            return this.httpService.axiosRef(originalRequest);
          } catch (refreshError) {
            this.refreshSubscribers = [];
            return Promise.reject(refreshError);
          } finally {
            this.isRefreshing = false;
          }
        }
        return Promise.reject(error);
      }
    );
  }

  // 改成异步方法,确保Token获取完成
  private async trySaveToken() {
    const response = await this.httpService.post(this.authUrl).toPromise();
    this.token = 'Bearer ' + response.data.access_token;
  }
}

2. 在模块中注册拦截器

因为这个拦截器实现了OnModuleInit,模块初始化时会自动执行初始化逻辑:

@Module({
  providers: [AuthTokenInterceptor],
})
export class AppModule {}

关键说明

  • Axios拦截器才是处理出站请求的正确方式:Nest的Interceptor管控的是「入站请求→控制器→响应客户端」的流程,和你的应用发出去的请求无关。
  • 异步Token初始化:用async/await等待Token获取完成,避免初始请求拿不到有效Token。
  • 防重复刷新:用isRefreshing标记和订阅队列,避免多个请求同时触发Token刷新接口。
  • 正确重试:调用this.httpService.axiosRef(originalRequest)才是真正重新发起请求,之前返回配置对象的逻辑完全无效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 00:43:16