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
相关产品推荐
相关产品推荐

