NestJS Monorepo中如何创建通用外部JWT认证守卫?
NestJS Monorepo通用JWT认证守卫依赖注入解决方案
我的Monorepo结构如下,包含多个独立部署的NestJS应用,以及一个存放通用认证守卫的公共包:
- root - - packages - - - app1 - - - app2 - - - guards - - - - src - - - - dist
公司要求公共包构建后从dist目录导入,当前在应用中使用守卫的方式是:
// app1/src/main.ts import { AuthGuard } from 'guards/dist'; async function bootstrap() { // ... app.useGlobalGuards(new AuthGuard()); // ... }
按照NestJS官方文档编写的守卫代码如下:
@Injectable() export class AuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { // JWT解析逻辑... return true; } }
现在的问题是:使用new AuthGuard()必须手动传入jwtService,但我不想每个应用都单独引入JwtService,违背了通用守卫的设计初衷。
我之前考虑的几个方案都有明显缺陷:
- 将guards包改为导出模块:不清楚如何以此实现守卫
- 弃用JwtService改用其他库:只能解决当前问题,无法应对未来类似场景
- 手动实例化JwtService:违背Nest架构设计
正确解决方案:封装守卫模块,利用Nest的依赖注入机制
1. 在公共guards包中创建模块
在guards/src下创建auth-guard.module.ts,将守卫和JWT相关服务封装到模块中,同时支持传入JWT配置(让各个应用自定义密钥等参数):
import { Module, DynamicModule } from '@nestjs/common'; import { JwtModule, JwtModuleOptions } from '@nestjs/jwt'; import { AuthGuard } from './auth.guard'; @Module({}) export class AuthGuardModule { static forRoot(options: JwtModuleOptions): DynamicModule { return { module: AuthGuardModule, imports: [JwtModule.register(options)], providers: [AuthGuard], exports: [AuthGuard], // 导出守卫供外部使用 }; } }
2. 调整守卫代码,保持依赖注入
守卫代码无需修改,继续通过依赖注入获取JwtService:
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); const token = this.extractTokenFromHeader(request); if (!token) { throw new UnauthorizedException(); } try { const payload = await this.jwtService.verifyAsync(token); request.user = payload; } catch { throw new UnauthorizedException(); } return true; } private extractTokenFromHeader(request: Request): string | undefined { const [type, token] = request.headers.authorization?.split(' ') ?? []; return type === 'Bearer' ? token : undefined; } }
3. 在应用中导入模块并获取守卫
在应用的根模块(比如app1/src/app.module.ts)中导入公共守卫模块,传入JWT配置:
import { Module } from '@nestjs/common'; import { AuthGuardModule } from 'guards/dist'; @Module({ imports: [ AuthGuardModule.forRoot({ secret: process.env.JWT_SECRET, // 每个应用可自定义密钥 signOptions: { expiresIn: '60s' }, }), ], }) export class AppModule {}
然后在main.ts中,通过app.get()方法从Nest的依赖注入容器中获取守卫实例,无需手动new:
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AuthGuard } from 'guards/dist'; async function bootstrap() { const app = await NestFactory.create(AppModule); // 从容器中获取守卫实例,自动注入JwtService const authGuard = app.get(AuthGuard); app.useGlobalGuards(authGuard); await app.listen(3000); } bootstrap();
方案优势
- 完全遵循Nest的依赖注入架构,不破坏设计原则
- 通用守卫的逻辑集中维护,各个应用只需传入配置即可复用
- 未来扩展其他依赖注入的公共组件时,可复用相同的模块封装方式
内容的提问来源于stack exchange,提问作者Ronny Efronny
相关产品推荐
相关产品推荐

