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

NestJS生成JWT令牌时返回空对象问题求助

NestJS JWT生成返回空对象排查与解决

问题描述

在NestJS中使用jsonwebtoken和@nestjs/jwt实现JWT令牌功能时,执行到生成令牌的代码行时,服务器返回空对象{},无任何错误提示。尝试直接使用jsonwebtoken包替代@nestjs/jwt,结果仍返回空对象,无法生成令牌。

相关代码

JwtStrategy代码

import { ConfigService } from "@nestjs/config";
import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Inject, Injectable, UnauthorizedException } from "@nestjs/common";
import { RidersService } from "../riders/riders.service";
import { DriversService } from "../drivers/drivers.service";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private readonly riderService: RidersService,
    private readonly driverService: DriversService,
    @Inject(ConfigService) config: ConfigService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: config.get("JWT_SECRET"),
    });
  }

  async validate(payload: {
    role: string;
    iat: number;
    exp: number;
    phone: string;
  }) {
    if (!payload) {
      throw new UnauthorizedException();
    }

    const service = {
      rider: this.riderService,
      driver: this.driverService,
    };

    const user = await service[payload.role].findByAny(payload.phone);
    if (!user) {
      throw new UnauthorizedException();
    }

    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    const { iat, exp, ...rest } = payload;

    return rest;
  }
}

AuthService代码

import { InjectModel } from "@nestjs/mongoose";
import { Injectable, Logger, HttpException } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
@Injectable()
export class AuthService {
  private readonly logger = new Logger(AuthService.name);

  constructor(
    private readonly jwtService: JwtService,
  ) {}
  async register(
    phone: string,
    role: string,
  ): Promise<LoginResponseDTO | GENERIC_RESPONSE | unknown> {
    return {
      token: this.jwtService.sign({ role, phone }),
    };
  }
}

执行return { token: this.jwtService.sign({ role, phone }), }时,代码执行至此停止并返回空响应。

AuthModule代码

import { JwtStrategy } from "./jwt.strategy";

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 { ConfigModule, ConfigService } from "@nestjs/config";


@Module({
  imports: [
    PassportModule,

    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get<string>("JWT_SECRET"),
        signOptions: { expiresIn: configService.get<string>("JWT_EXPIRESIN") },
      }),
      inject: [ConfigService],
    }),
  ],
  providers: [
    AuthService,
    JwtStrategy,
  ],
  exports: [AuthService],
  controllers: [AuthController],
})
export class AuthModule {}

AuthController代码

import { AuthService } from "./auth.service";
import { BadRequestException, Controller, Post, Query } from "@nestjs/common";
import { ParsePhonePipe } from "./../pipes/transform-phone.pipes";

@Controller("auth")
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post("signup")
  async register(
    @Query("phone", new ParsePhonePipe()) phone: string,
    @Query("role") role: string,
  ) {
    if (!role) {
      throw new BadRequestException("user role is missing");
    }
    try {
      const response = await this.authService.register(phone, role);
      console.log(response, "RESPONSE");
      return response;
    } catch (error) {
      return error;
    }
  }
}

AppModule代码

import { ConfigModule, ConfigService } from "@nestjs/config";
import { Module } from "@nestjs/common";
import { APP_GUARD } from "@nestjs/core";

import { AuthModule } from "./auth/auth.module";

import { JwtAuthGuard } from "./auth/jwt.auth.guard";


@Module({
  imports: [
    AuthModule,
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.get<string>("RYDR_DB_URI"),
      }),
      inject: [ConfigService],
    }),

    ConfigModule.forRoot({
      isGlobal: true,
    }),
  ],
  providers: [
    {
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    },
    TripHistoryService,
    DriversService,
  ],
})
export class AppModule {}

排查与解决方法

1. 全局JWT守卫拦截注册请求

你的AppModule注册了全局JwtAuthGuard,但/auth/signup是无需授权即可访问的接口,守卫会优先校验请求头中的Bearer Token,无令牌时可能直接返回空对象而非正常错误。

解决步骤:

  • 定义Public装饰器标记无需验证的接口:
    // src/auth/public.decorator.ts
    import { SetMetadata } from '@nestjs/common';
    
    export const IS_PUBLIC_KEY = 'isPublic';
    export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
    
  • 修改JwtAuthGuard的canActivate方法,跳过标记为Public的接口:
    import { ExecutionContext, Injectable } from '@nestjs/common';
    import { Reflector } from '@nestjs/core';
    import { JwtAuthGuard as NestJwtAuthGuard } from '@nestjs/passport';
    import { IS_PUBLIC_KEY } from './public.decorator';
    
    @Injectable()
    export class JwtAuthGuard extends NestJwtAuthGuard {
      constructor(private reflector: Reflector) {
        super();
      }
    
      canActivate(context: ExecutionContext) {
        const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
          context.getHandler(),
          context.getClass(),
        ]);
        if (isPublic) {
          return true;
        }
        return super.canActivate(context);
      }
    }
    
  • 在signup接口添加@Public()装饰器:
    @Public()
    @Post("signup")
    async register(
      @Query("phone", new ParsePhonePipe()) phone: string,
      @Query("role") role: string,
    ) {
      // 原有逻辑
    }
    

2. 环境变量加载顺序错误

AppModule中ConfigModule.forRoot()放在AuthModule之后,导致AuthModule初始化时环境变量未加载完成,JWT_SECRET为空,jwtService.sign()静默失败返回空。

解决:将ConfigModule.forRoot()移到imports数组最前面:

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
    }),
    AuthModule,
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.get<string>("RYDR_DB_URI"),
      }),
      inject: [ConfigService],
    }),
  ],
  // 其他配置
})
export class AppModule {}

3. 管道逻辑异常未捕获

检查ParsePhonePipe的实现,若管道处理phone参数时抛出异常,可能因全局守卫拦截导致异常未正常返回,最终返回空对象。可暂时移除ParsePhonePipe测试,确认是否能正常生成令牌。

4. 增加日志排查

在AuthService的register方法中添加日志,确认jwtService.sign()是否执行并生成令牌:

async register(phone: string, role: string) {
  const token = this.jwtService.sign({ role, phone });
  this.logger.log(`Generated token: ${token}`);
  return { token };
}

若日志无输出,说明代码未执行到此处,大概率是全局守卫的拦截问题。


内容的提问来源于stack exchange,提问作者Pete Ceph

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 18:32:29