NestJS JwtModule.registerAsync注册延迟致依赖无法解决求助
问题:NestJS JwtModule.registerAsync全局注册后依赖无法解析
在未使用ConfigModule的场景下,通过AWS Secrets Manager异步获取JWT密钥,在AuthModule中用JwtModule.registerAsync全局注册JwtModule,但注入JwtService的MyGuard所在BenefitsModule出现依赖解析错误:
ERROR [ExceptionHandler] Nest can't resolve dependencies of the MyGuard (?). Please make sure that the argument JwtService at index [0] is available in the BenefitsModule context.
改用register方式,在main.ts手动获取密钥存入process.env则运行正常。排查发现,retrieveJwtSecret的异步请求未完成时,Nest已加载依赖JwtModule的模块,需要让Nest等待异步依赖完成后再加载相关模块。
相关代码:
// secret-key.helper.ts /* Retrieve the JWT secret value from AWS Secrets Manager. This is used to sign JWTs. Dont worry about imports, they're there. */ async function retrieveJwtSecret(): Promise<string> { const secretClient = new SecretsManagerClient({ region: process.env.AWS_REGION, }); const getSecretCom = new GetSecretValueCommand({ SecretId: process.env.JWT_SECRET_ARN, }); const jwtSecret = (await secretClient.send(getSecretCom)).SecretString; return jwtSecret; }
// auth.module.ts import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { JwtModule } from '@nestjs/jwt'; import { retrieveJwtSecret } from '../../utils/secret-key.helper'; @Module({ imports: [ JwtModule.registerAsync({ useFactory: async () => ({ global: true, secret: await retrieveJwtSecret(), signOptions: { expiresIn: '16h' }, }), }), ], controllers: [AuthController], providers: [AuthService], }) export class AuthModule {}
// my.guard.ts import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Request } from 'express'; import { logEvent } from '../log-event.helper'; import { JwtService } from '@nestjs/jwt'; import { JwtPayloadDto } from 'src/models/auth/dto/jwt-payload.dto'; @Injectable() export class MyGuard implements CanActivate { constructor(private jwtService: JwtService) {} private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } async canActivate(context: ExecutionContext): Promise<boolean> { const req = context.switchToHttp().getRequest(); /* JWT Authentication */ const token = this.extractTokenFromHeader(req); if (!token) { logEvent('ERROR: No token found.', req.headers); return false; } else { const payload = this.jwtService.decode(token) as JwtPayloadDto; logEvent('Token found.', payload); return true; } } }
解决方案
1. 提前在启动阶段加载异步密钥
在main.ts中先完成JWT密钥的异步获取,再启动Nest应用,确保所有模块加载时密钥已就绪:
// main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { retrieveJwtSecret } from './utils/secret-key.helper'; async function bootstrap() { // 先获取JWT密钥并存入环境变量 process.env.JWT_SECRET = await retrieveJwtSecret(); const app = await NestFactory.create(AppModule); await app.listen(3000); } bootstrap();
然后修改AuthModule的JwtModule配置为同步register:
// auth.module.ts @Module({ imports: [ JwtModule.register({ global: true, secret: process.env.JWT_SECRET, signOptions: { expiresIn: '16h' }, }), ], controllers: [AuthController], providers: [AuthService], }) export class AuthModule {}
2. 确保全局模块注册时机正确
如果坚持使用registerAsync,需要确保AuthModule被根模块(AppModule)优先导入,且全局模块的配置能被其他模块正确识别。核心是保证AuthModule在BenefitsModule之前加载:
// app.module.ts @Module({ imports: [ AuthModule, // 先导入AuthModule,确保全局JwtModule先初始化 BenefitsModule, ], }) export class AppModule {}
Nest的全局模块需要在应用启动时完成初始化,registerAsync的useFactory会被异步执行,但如果其他模块在AuthModule初始化完成前就尝试注入JwtService,就会报错。因此提前在main.ts加载密钥是最可靠的方案,因为它确保了所有模块启动前密钥已准备好。
内容的提问来源于stack exchange,提问作者Sa'id Kharboutli
相关产品推荐
相关产品推荐

