乐闻世界logo
搜索文章和话题

NestJS 中间件和守卫的区别是什么?

2月17日 22:34

中间件(Middleware)的概念

中间件是在路由处理程序之前调用的函数,可以访问请求和响应对象,以及应用程序的请求-响应周期中的 next() 中间件函数。中间件主要用于:

  • 执行任何代码
  • 更改请求和响应对象
  • 结束请求-响应周期
  • 调用堆栈中的下一个中间件函数

中间件的基本结构

typescript
import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; @Injectable() export class LoggerMiddleware implements NestMiddleware { use(req: Request, res: Response, next: NextFunction) { console.log(`Request... ${req.method} ${req.url}`); next(); } }

应用中间件

在模块中应用中间件

typescript
import { Module, MiddlewareConsumer, NestModule } from '@nestjs/common'; import { LoggerMiddleware } from './logger.middleware'; @Module({ imports: [], controllers: [], providers: [], }) export class AppModule implements NestModule { configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .forRoutes('cats'); } }

限制中间件应用到特定路由

typescript
configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .exclude('cats', { path: 'cats', method: RequestMethod.GET }) .forRoutes(CatsController); }

应用多个中间件

typescript
configure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware, AuthMiddleware) .forRoutes(CatsController); }

函数式中间件

typescript
export function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request...`); next(); } // 应用函数式中间件 configure(consumer: MiddlewareConsumer) { consumer .apply(logger) .forRoutes(CatsController); }

全局中间件

typescript
import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { logger } from './common/middleware/logger.middleware'; async function bootstrap() { const app = await NestFactory.create(AppModule); app.use(logger); await app.listen(3000); } bootstrap();

守卫(Guards)的概念

守卫是一个使用 @Injectable() 装饰器的类,实现了 CanActivate 接口。守卫负责确定请求是否应该由路由处理程序处理。主要用于:

  • 身份验证
  • 授权
  • 权限检查

守卫的基本结构

typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); return this.validateRequest(request); } private validateRequest(request: any): boolean { // 验证逻辑 return true; } }

使用守卫

在控制器上使用守卫

typescript
@Controller('cats') @UseGuards(AuthGuard) export class CatsController { @Get() findAll() { return 'This action returns all cats'; } }

在特定路由上使用守卫

typescript
@Controller('cats') export class CatsController { @Get() @UseGuards(AuthGuard) findAll() { return 'This action returns all cats'; } }

全局守卫

typescript
import { Module } from '@nestjs/common'; import { APP_GUARD } from '@nestjs/core'; import { AuthGuard } from './auth.guard'; @Module({ providers: [ { provide: APP_GUARD, useClass: AuthGuard, }, ], }) export class AppModule {}

角色守卫示例

typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; @Injectable() export class RolesGuard implements CanActivate { constructor(private reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [ context.getHandler(), context.getClass(), ]); if (!requiredRoles) { return true; } const { user } = context.switchToHttp().getRequest(); return requiredRoles.some((role) => user.roles?.includes(role)); } } // 创建角色装饰器 import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); // 使用角色守卫 @Controller('cats') @UseGuards(RolesGuard) export class CatsController { @Get() @Roles('admin') findAll() { return 'This action returns all cats'; } }

中间件 vs 守卫

中间件的特点

  • 在 Express 中间件链中执行
  • 可以访问请求和响应对象
  • 在路由处理程序之前执行
  • 适用于全局逻辑(如日志记录、CORS)
  • 不了解路由处理程序

守卫的特点

  • 在 NestJS 依赖注入系统中执行
  • 可以访问 ExecutionContext
  • 在中间件之后、拦截器之前执行
  • 适用于权限和授权逻辑
  • 了解路由处理程序和类

选择指南

  • 使用中间件:日志记录、请求解析、CORS、压缩等
  • 使用守卫:身份验证、授权、权限检查等

自定义装饰器

从请求中提取用户

typescript
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; export const User = createParamDecorator( (data: string | undefined, ctx: ExecutionContext) => { const request = ctx.switchToHttp().getRequest(); const user = request.user; return data ? user?.[data] : user; }, ); // 使用自定义装饰器 @Get() findOne(@User('id') userId: string) { return this.catsService.findOne(userId); }

JWT 认证守卫示例

typescript
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class JwtAuthGuard 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; } }

最佳实践

  1. 职责分离:中间件用于全局逻辑,守卫用于权限控制
  2. 使用装饰器:创建自定义装饰器简化守卫使用
  3. 错误处理:在守卫中抛出适当的异常
  4. 性能考虑:避免在守卫中执行耗时操作
  5. 测试覆盖:为中间件和守卫编写测试
  6. 文档化:为中间件和守卫添加清晰的文档
  7. 避免过度使用:只在需要时使用中间件和守卫

总结

NestJS 中间件和守卫系统提供了:

  • 灵活的请求处理机制
  • 强大的权限控制能力
  • 清晰的关注点分离
  • 易于测试和维护的代码结构

掌握中间件和守卫是构建安全、可维护的 NestJS 应用程序的关键。中间件处理全局逻辑,守卫处理权限控制,它们共同构成了应用程序的安全和功能基础。

标签:NestJS