Nest中使用passport-jwt的secretOrKeyProvider返回401,validate未触发
我是NestJS新手,想要使用passport-jwt、@nestjs/passport和Firebase构建应用的认证部分,但一直收到HTTP 401响应,且发现FirebaseStrategy中的validate方法没有被调用,按道理每次secretOrKeyProvider验证HTTP头中的JWT后都应该调用它,这该如何解决?
我的策略代码:
import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable } from '@nestjs/common'; import { getAuth } from 'firebase-admin/auth'; @Injectable() export class FirebaseStrategy extends PassportStrategy(Strategy, 'firebase') { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, secretOrKeyProvider: async (request, rawJwtToken, done) => { try { const decodedToken = await getAuth().verifyIdToken(rawJwtToken); done(null, decodedToken); } catch (error) { done(error); } }, }); } async validate(payload: any) { console.log('validate'); return payload; } }
我的守卫代码:
import { Injectable } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; @Injectable() export class FirebaseAuthGuard extends AuthGuard('firebase') {}
我的auth.module.ts代码:
import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { UsersModule } from '../users/users.module'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; import { FirebaseStrategy } from './firebase.stragety'; import { AuthController } from './auth.controller'; @Module({ controllers: [AuthController], imports: [UsersModule, PassportModule, JwtModule.register({})], providers: [AuthService, FirebaseStrategy], exports: [AuthService], }) export class AuthModule {}
我的控制器代码:
import { Controller, Get, Request, UseGuards } from '@nestjs/common'; import { AuthService } from './auth.service'; import { FirebaseAuthGuard } from './firebase.guard'; @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @UseGuards(FirebaseAuthGuard) @Get('login') async logIn(@Request() req) { return 'login'; } }
问题原因分析
核心问题是错误使用了secretOrKeyProvider:passport-jwt的secretOrKeyProvider设计初衷是提供用于验证JWT签名的密钥/公钥,而非直接完成Token的全流程验证。你在该方法中调用Firebase的verifyIdToken并返回解码后的Token,会导致passport-jwt尝试用这个返回值去校验JWT签名,逻辑完全错误,最终验证失败返回401,validate方法根本不会被触发。
另外,auth.module.ts中存在拼写错误:导入FirebaseStrategy时路径写成了./firebase.stragety(stragety应为strategy),这会导致Strategy无法被正确注册,也是引发401的潜在原因。
解决方案
重构Strategy的验证逻辑
移除secretOrKeyProvider,将Firebase的Token验证逻辑移到validate方法中,这才是符合passport-jwt流程的正确做法:import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { getAuth } from 'firebase-admin/auth'; @Injectable() export class FirebaseStrategy extends PassportStrategy(Strategy, 'firebase') { constructor() { super({ jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), ignoreExpiration: false, // 这里填任意占位值,因为Firebase会自行处理签名校验 secretOrKey: 'placeholder-secret', }); } async validate(rawJwtToken: string) { try { const decodedToken = await getAuth().verifyIdToken(rawJwtToken); console.log('validate'); return decodedToken; } catch (error) { throw new UnauthorizedException('无效的Firebase Token'); } } }修正模块中的拼写错误
将auth.module.ts中的导入路径修正为正确的拼写:import { FirebaseStrategy } from './firebase.strategy';优化路由语义
@Get('login')路由搭配FirebaseAuthGuard语义矛盾——登录接口是用来获取Token的,不应要求携带Token访问。建议将受保护的接口改为其他路由,例如:@UseGuards(FirebaseAuthGuard) @Get('profile') async getProfile(@Request() req) { return req.user; }
内容的提问来源于stack exchange,提问作者Yan Zhihou

