JwtModule.registerAsync在NestJS中无法正常工作

3
我正在开发一个NestJS项目,需要使用JWT和.env配置。它可以生成令牌,但是在尝试访问受保护的URL(带有授权头)时,只会返回未经授权的消息。

jwt.strategy.ts

import { Injectable, UnauthorizedException, Logger } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { JwtPayload } from './interfaces/jwt-payload.interface';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {

    constructor(private readonly authService: AuthService) {
        super({
            jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
            secretOrKey: process.env.JWT_SECRET_KEY,
        });
    }

    async validate(payload: JwtPayload) {
        const user = await this.authService.validateUser(payload);
        if (!user) {
            throw new UnauthorizedException();
        }

        return user;
    }
}

auth.module.ts

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.registerAsync({
      useFactory: async () => ({
        secretOrPrivateKey: process.env.JWT_SECRET_KEY,
        signOptions: {
          expiresIn: process.env.JWT_EXPIRATION_TIME,
        },
      }),
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
})
export class AuthModule {}

main.ts

import { NestFactory } from '@nestjs/core';
import * as dotenv from 'dotenv';
import { ApiModule } from './api/api.module';
import { Logger } from '@nestjs/common';

async function bootstrap() {
  dotenv.config({ path: './.env'});
  const app = await NestFactory.create(ApiModule);
  const port = process.env.APP_PORT;

  await app.listen(port);
  Logger.log(`Server started on http://localhost:${port}`);
}
bootstrap();

看起来JwtModule.registerAsync与环境变量不兼容。我尝试了很多方法,但它总是失败。 如果我在auth.module.ts中更改环境变量以使用静态数据,则可以正常工作。 像这样:

secretOrPrivateKey: 'secretKey',
signOptions: {
  expiresIn: 3600,
},

更新 项目结构

- src
    - api
        - auth
            - interfaces
                jwt-payload.interface.ts
            auth.controller.ts
            auth.module.ts
            auth.service.ts
            jwt.strategy.ts
            index.ts
        api.module.ts
        index.ts
    main.ts
- test
.env

现在我的main.ts看起来像这样。

import { NestFactory } from '@nestjs/core';
import * as dotenv from 'dotenv';
import { resolve } from 'path';
import { ApiModule } from './api/api.module';
import { Logger } from '@nestjs/common';

async function bootstrap() {
  dotenv.config({ path: resolve(__dirname, '../.env') });
  const app = await NestFactory.create(ApiModule);
  const port = process.env.APP_PORT;

  await app.listen(port);
  Logger.log(`Server started on http://localhost:${port}`);
}
bootstrap();

你看到我的.env文件在项目的根目录下。

5个回答

16

如果您使用配置模块,您可以像这样操作:

JwtModule.registerAsync({
  useFactory: (config: ConfigService) => {
    return {
      secret: config.get<string>('JWT_SECRET_KEY'),
      signOptions: {
        expiresIn: config.get<string | number>('JWT_EXPIRATION_TIME'),
      },
    };
  },
  inject: [ConfigService],
}),

我也遇到了初始化 JwtModule 的问题,但这段代码解决了它。


1
我在读取环境文件中的密钥时遇到了问题,这个解决方案对我很有效。 - Eben

3

简而言之

假设你有一个位于正确位置的.env文件,为了使此工作正常,你需要在所有内容之前先配置dotenv,即使是导入也是如此。

// configure dotenv before every thing, even imports
import * as dotenv from 'dotenv';
import { resolve } from 'path';
dotenv.config({ path: resolve(__dirname, '../.env') });

// rest of the code
import { NestFactory } from '@nestjs/core';
import { ApiModule } from './api/api.module';
import { Logger } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(ApiModule);
  const port = process.env.APP_PORT;

  await app.listen(port);
  Logger.log(`Server started on http://localhost:${port}`);
}
bootstrap();

为什么?

因为当你像这样做某事时

import { ApiModule } from './api/api.module'

发生的情况是你正在运行来自文件 ./api/api.module 的代码,这个文件看起来像这样(我使用了你在问题中展示的其他文件,以便更加清晰易懂)。
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    PassportModule.register({ defaultStrategy: 'jwt' }),
    JwtModule.registerAsync({
      useFactory: async () => ({
        secretOrPrivateKey: process.env.JWT_SECRET_KEY,
        signOptions: {
          expiresIn: process.env.JWT_EXPIRATION_TIME,
        },
      }),
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
})
export class AuthModule {}

当你导入它时,整个文件将被“执行”,在设置`dotenv`之前,`process.env`的引用将被“设置”。
所以在使用`process.env`的代码之前,需要“运行”设置`dotenv`的代码。
观察:
我仍然建议使用已经内置的配置,然后您应该使用异步方法并注入配置服务(就像其他答案一样)。
但是如果您确实想使用`process.env`,则在所有内容之前设置`dotenv`是正确的方法。

1

我最近也在做这个项目,和你一样尝试了很多方法,但都没有成功……然而,那一个方法却奏效了!

 @Module({ 
    imports: [
    AnyModule,
    JwtModule.registerAsync({
      useFactory: async _ => {
        return {
          secret: process.env.JWT_SECRET,
          signOptions: { expiresIn: "120s" }
        }
      }
    }),
  ]

我使用 JwtModule.registerAsync() 方法,配合 asyncuseFactory,并且不需要注入 ConfigService

然后在我的 AppModule.ts 中使用。

@Module({
    imports: [
      ConfigModule.forRoot({
        isGlobal: true,
      })

通过这个,我们告诉ConfigModule它是全局的,我们可以在任何模块中使用它而不需要导入。

简而言之,我们需要导入JwtModule并调用userFactory对象的async方法registerAsync。完成所有这些后,JwtModule等待AppModule.ts被创建以调用我们的process.env,实际上是调用ConfigModule

感谢大家的回答和NestJs文档

希望这对您有所帮助。


1

使用 parseInt() 处理 process.env.JWT_EXPIRATION_TIMEexpiresIn 参数应该是一个数字。在你的情况下它是一个字符串。


这解决了我的同样的问题! - Anarno

0

对我来说,你的代码可以正常工作:

Edit Nest.js JWT Auth

你的 .env 文件在哪里? 你的配置 dotenv.config({ path: './.env'}); 等同于默认配置 dotenv.config();,其中 .env 文件在项目根目录中查找(而不是在 src 中)。
如果你想把你的 .env 文件放在 src 目录中,请使用以下配置。
import { resolve } from 'path';
dotenv.config({ path: resolve(__dirname, '.env') });

建议您不要直接使用环境变量,而是将它们封装在一个ConfigService中,详见文档。这样可以更轻松地进行测试和重构。


我不知道可能是什么问题:S。我确定本地代码是一样的,但每次都会抛出未授权的错误。 - Syxkno
当您在JwtStrategy中使用console.log打印process.env.JWT_SECRET_KEY时会发生什么?它是未定义的吗?它在JwtModule导入中被定义了吗? - Kim Kern
我在JwtModule的声明中和JwtStrategy中添加了日志。它们记录了JWTModule: secretKeyJWTStrategy: secretKey,这两个值都是有效的。 - Syxkno
如果你的代码在另一台机器上能够正常运行,并且环境变量已经正确解析,那么我唯一能想到的就是你的依赖项。请确保使用 npm update 运行最新版本,并完全清除并重新安装你的依赖项。除此之外,我没有其他想法了。 - Kim Kern
我创建了一个自定义的JwtAuthGuard并在handleRequest方法里记录了所有内容... 不知何故,expireIn: 3600没有起作用。我将其改为1d,然后它就正常工作了。感谢您的帮助! - Syxkno
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接