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

NestJS多租户集成Passport JWT异常:Request对象未定义

NestJS多租户:JwtStrategy验证阶段Request对象注入丢失问题

问题分析

核心问题是:请求作用域的数据库连接工厂在JwtStrategy的validate方法执行时,无法注入REQUEST对象(变为undefined),导致无法获取子域名建立租户连接,进而validateAccessToken无法执行完成。登录请求正常是因为登录流程未触发JWT守卫的验证逻辑,而登录后的请求会经过JWT守卫,此时连接工厂被二次触发时丢失了请求上下文。

可能的原因

  1. JWT守卫阶段的请求上下文传递问题:NestJS守卫执行时机早于控制器,通过moduleRef.resolve获取请求作用域的AuthService时,请求上下文的关联可能存在遗漏,导致依赖的CONNECTION工厂无法正确注入REQUEST。
  2. 连接工厂未处理异步操作:如果getTenantConnection是异步方法(比如创建TypeORM连接),但工厂的useFactory未使用async/await,会导致连接未正确初始化,进而引发后续错误。
  3. REQUEST注入的作用域冲突:JwtStrategy本身是单例作用域,在其内部解析请求作用域服务时,请求上下文的绑定可能未正确传递到依赖链的下游(即连接工厂)。

解决方案

方案1:使用AsyncLocalStorage存储租户上下文(推荐)

通过AsyncLocalStorage在请求早期存储子域名,避免后续依赖REQUEST注入,彻底解决上下文丢失问题:

  1. 创建租户上下文存储
// src/tenancy/tenancy.context.ts
import { AsyncLocalStorage } from 'async_hooks';

export const tenancyStorage = new AsyncLocalStorage<string>();
  1. 添加租户中间件提取子域名
// src/tenancy/tenancy.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { tenancyStorage } from './tenancy.context';

@Injectable()
export class TenancyMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    const subdomain = req.subdomains[0]; // 根据你的域名结构调整提取逻辑
    if (subdomain) {
      tenancyStorage.run(subdomain, () => next());
    } else {
      throw new Error('Missing tenant subdomain');
    }
  }
}
  1. 全局注册中间件
    在AppModule的configure方法中注册:
configure(consumer: MiddlewareConsumer) {
  consumer.apply(TenancyMiddleware).forRoutes('*');
}
  1. 修改连接工厂使用上下文存储
export const connectionFactory = {
  provide: CONNECTION,
  scope: Scope.REQUEST,
  useFactory: async () => {
    const subdomain = tenancyStorage.getStore();
    if (!subdomain) {
      throw new Error('Tenant subdomain not found in context');
    }
    return await getTenantConnection(subdomain); // 确保异步操作使用await
  },
};

方案2:确保请求上下文正确传递到连接工厂

如果坚持使用REQUEST注入,需确保moduleRef.resolve时的上下文正确传递到依赖链:

  1. 标记AuthService为请求作用域
    如果AuthService依赖请求作用域的CONNECTION,需将其也设为请求作用域:
@Injectable({ scope: Scope.REQUEST })
export class AuthService {
  constructor(@Inject(CONNECTION) private readonly connection: Connection) {}
  // ...
}
  1. 修改连接工厂为异步
    如果getTenantConnection是异步方法,必须让工厂返回Promise:
export const connectionFactory = {
  provide: CONNECTION,
  scope: Scope.REQUEST,
  useFactory: async (request: Request) => {
    console.log('request', request);
    if (!request) {
      throw new Error('Request object is undefined');
    }
    const subdomain = request.subdomains[0]; // 调整子域名提取逻辑
    if (!subdomain) {
      throw new Error('Missing tenant subdomain');
    }
    return await getTenantConnection(subdomain);
  },
  inject: [REQUEST],
};
  1. 在JwtStrategy中显式传递上下文
    确保moduleRef.resolve时正确使用请求上下文:
async validate(req: Request, payload: any): Promise<any> {
  const { jti } = payload;
  const contextId = ContextIdFactory.getByRequest(req);
  // 确保解析时传递上下文,并且AuthService是请求作用域
  this.authService = await this.moduleRef.resolve(AuthService, contextId);

  try {
    const user = await this.authService.validateAccessToken(jti);
    console.log('here');
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  } catch (error) {
    console.error('Error validating access token:', error);
    throw new UnauthorizedException();
  }
}

方案3:手动传递子域名到AuthService

绕过连接工厂的REQUEST依赖,直接在validate方法中提取子域名并传递给AuthService:

  1. 修改AuthService的validateAccessToken方法
async validateAccessToken(jti: string, subdomain: string) {
  const connection = await getTenantConnection(subdomain);
  // 使用connection查询用户
  return connection.getRepository(User).findOne({ where: { tokenId: jti } });
}
  1. 在JwtStrategy中传递子域名
async validate(req: Request, payload: any): Promise<any> {
  const { jti } = payload;
  const subdomain = req.subdomains[0];
  
  try {
    const user = await this.authService.validateAccessToken(jti, subdomain);
    console.log('here');
    if (!user) {
      throw new UnauthorizedException();
    }
    return user;
  } catch (error) {
    console.error('Error validating access token:', error);
    throw new UnauthorizedException();
  }
}

验证步骤

  1. 启动应用后,发送登录请求确认正常。
  2. 使用返回的token发送后续请求,检查是否输出here日志。
  3. 查看数据库连接是否正确关联子域名对应的租户库。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 08:14:49