中间件(Middleware)的概念
中间件是在路由处理程序之前调用的函数,可以访问请求和响应对象,以及应用程序的请求-响应周期中的 next() 中间件函数。中间件主要用于:
- 执行任何代码
- 更改请求和响应对象
- 结束请求-响应周期
- 调用堆栈中的下一个中间件函数
中间件的基本结构
typescriptimport { 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(); } }
应用中间件
在模块中应用中间件
typescriptimport { 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'); } }
限制中间件应用到特定路由
typescriptconfigure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware) .exclude('cats', { path: 'cats', method: RequestMethod.GET }) .forRoutes(CatsController); }
应用多个中间件
typescriptconfigure(consumer: MiddlewareConsumer) { consumer .apply(LoggerMiddleware, AuthMiddleware) .forRoutes(CatsController); }
函数式中间件
typescriptexport function logger(req: Request, res: Response, next: NextFunction) { console.log(`Request...`); next(); } // 应用函数式中间件 configure(consumer: MiddlewareConsumer) { consumer .apply(logger) .forRoutes(CatsController); }
全局中间件
typescriptimport { 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 接口。守卫负责确定请求是否应该由路由处理程序处理。主要用于:
- 身份验证
- 授权
- 权限检查
守卫的基本结构
typescriptimport { 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'; } }
全局守卫
typescriptimport { 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 {}
角色守卫示例
typescriptimport { 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、压缩等
- 使用守卫:身份验证、授权、权限检查等
自定义装饰器
从请求中提取用户
typescriptimport { 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 认证守卫示例
typescriptimport { 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; } }
最佳实践
- 职责分离:中间件用于全局逻辑,守卫用于权限控制
- 使用装饰器:创建自定义装饰器简化守卫使用
- 错误处理:在守卫中抛出适当的异常
- 性能考虑:避免在守卫中执行耗时操作
- 测试覆盖:为中间件和守卫编写测试
- 文档化:为中间件和守卫添加清晰的文档
- 避免过度使用:只在需要时使用中间件和守卫
总结
NestJS 中间件和守卫系统提供了:
- 灵活的请求处理机制
- 强大的权限控制能力
- 清晰的关注点分离
- 易于测试和维护的代码结构
掌握中间件和守卫是构建安全、可维护的 NestJS 应用程序的关键。中间件处理全局逻辑,守卫处理权限控制,它们共同构成了应用程序的安全和功能基础。