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

NestJS微服务:拦截出站HTTP请求并设置JWT令牌的安全疑问

问题解答

原方案的风险分析

你提到的全局拦截器修改Axios默认头的方案确实存在令牌混用的风险,你的担忧完全合理。因为httpService.axiosRef.defaults.headers.common是全局共享配置,当多个请求并发处理时,后一个请求的拦截器会覆盖全局Authorization头,导致前一个请求还未发出就使用了错误令牌,引发权限问题或数据泄露。

正确实现方案:基于AsyncLocalStorage的上下文隔离

要实现无参传递令牌且避免并发问题,可利用Node.js的AsyncLocalStorage为每个请求创建独立上下文,结合Axios请求拦截器动态注入令牌。

步骤1:创建请求上下文存储

// src/common/request-context.ts
import { AsyncLocalStorage } from 'async_hooks';
import { Injectable } from '@nestjs/common';

@Injectable()
export class RequestContext {
  private readonly als = new AsyncLocalStorage<Map<string, any>>();

  run<T>(callback: () => T): T {
    return this.als.run(new Map(), callback);
  }

  set(key: string, value: any): void {
    const store = this.als.getStore();
    if (store) store.set(key, value);
  }

  get(key: string): any {
    return this.als.getStore()?.get(key);
  }
}

步骤2:全局拦截器存储令牌到上下文

// src/common/interceptors/auth-token.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { RequestContext } from '../request-context';

@Injectable()
export class AuthTokenInterceptor implements NestInterceptor {
  constructor(private readonly requestContext: RequestContext) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    let token: string | undefined;

    // 处理HTTP请求
    if (context.getType() === 'http') {
      const req = context.switchToHttp().getRequest();
      token = req.headers['authorization'];
    }
    // 处理GraphQL请求
    else if (context.getType() === 'graphql') {
      const gqlCtx = context.getArgByIndex(2);
      token = gqlCtx.token;
    }

    if (token) {
      this.requestContext.set('authorization', token);
    }

    return next.handle();
  }
}

步骤3:自定义HttpService注入令牌

// src/common/providers/custom-http.service.ts
import { HttpService } from '@nestjs/axios';
import { Injectable, OnModuleInit } from '@nestjs/common';
import { RequestContext } from '../request-context';

@Injectable()
export class CustomHttpService extends HttpService implements OnModuleInit {
  constructor(private readonly requestContext: RequestContext) {
    super();
  }

  onModuleInit() {
    // 添加Axios请求拦截器,动态注入当前请求的令牌
    this.axiosRef.interceptors.request.use((config) => {
      const token = this.requestContext.get('authorization');
      if (token) {
        config.headers['Authorization'] = token;
      }
      return config;
    });
  }
}

步骤4:模块注册配置

// src/app.module.ts
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { HttpModule } from '@nestjs/axios';
import { RequestContext } from './common/request-context';
import { AuthTokenInterceptor } from './common/interceptors/auth-token.interceptor';
import { CustomHttpService } from './common/providers/custom-http.service';
import { UserController } from './user/user.controller';
import { VehicleService } from './vehicle/vehicle.service';

@Module({
  imports: [HttpModule],
  controllers: [UserController],
  providers: [
    RequestContext,
    {
      provide: APP_INTERCEPTOR,
      useClass: AuthTokenInterceptor,
    },
    {
      provide: HttpService,
      useClass: CustomHttpService,
    },
    VehicleService,
  ],
})
export class AppModule {}

步骤5:简化业务代码

现在控制器和服务无需再传递令牌参数:

user.controller.ts

@Controller('/users')
export class UserController {
  constructor(private userService: UserService, private vehicleService: VehicleService) {}

  @Post()
  create(@Body() userData: UserData) {
    const vehicle$ = this.vehicleService.getVehicleById(userData.vehicleId);
    // 后续保存用户逻辑
  }
}

vehicle.service.ts

@Injectable()
export class VehicleService {
  basePath = `http://ms-vehicles/api/v1/vehicles`;

  constructor(private httpService: HttpService) {}

  getVehicleById(vehicleId: string) {
    return this.httpService.get(`${this.basePath}/${vehicleId}`);
  }
}

方案优势

  • 上下文隔离:每个请求的令牌存储在独立的AsyncLocalStorage上下文中,并发请求间不会互相干扰
  • 无侵入性:业务代码无需关心令牌传递逻辑,降低耦合度
  • 扩展性强:后续如需传递其他请求上下文数据,只需在RequestContext中添加即可

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 15:24:22