NestJS 的 守卫(Guard) 是一个非常有用的功能,它主要用于权限验证和请求授权。简单来说,守卫决定了一个请求是否应该被路由处理器处理。
守卫是什么?
守卫是一个用 @Injectable() 装饰器注解的类,它实现了 CanActivate 接口。守卫基于 运行时反射 机制工作,在中间件之后、管道/拦截器之前执行。
守卫的核心职责: 返回 true 或 false,决定请求是否继续。
- 返回
true:请求继续执行
- 返回
false:请求被拒绝,NestJS 自动返回 403 Forbidden
守卫 vs 中间件 vs 管道 vs 拦截器
| 组件 |
执行时机 |
主要用途 |
| 中间件 |
最早 |
日志、CORS、请求体解析 |
| 守卫 |
中间件之后 |
鉴权、权限校验 |
| 管道 |
守卫之后 |
参数校验、类型转换 |
| 拦截器 |
管道之后 |
响应映射、异常处理 |
第一个自定义守卫
创建一个简单的认证守卫:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Observable } from 'rxjs';
@Injectable() export class AuthGuard implements CanActivate { canActivate( context: ExecutionContext, ): boolean | Promise<boolean> | Observable<boolean> { const request = context.switchToHttp().getRequest(); return validateRequest(request); } }
|
canActivate 方法接收 ExecutionContext 参数,它提供了当前请求的上下文信息。通过 context.switchToHttp().getRequest() 可以获取原生的请求对象。
使用守卫
绑定到单个路由
1 2 3 4 5 6 7 8 9 10 11 12
| import { Controller, Get, UseGuards } from '@nestjs/common'; import { AuthGuard } from './auth.guard';
@Controller('users') export class AppController { @Get('profile') @UseGuards(AuthGuard) getProfile() { return { message: 'This is a protected resource' }; } }
|
绑定到整个控制器
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Controller('users') @UseGuards(AuthGuard) export class UsersController { @Get('profile') getProfile() { return 'profile'; }
@Get('settings') getSettings() { return 'settings'; } }
|
全局守卫
1 2 3 4 5 6 7 8 9 10 11
| import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { AuthGuard } from './auth.guard';
async function bootstrap() { const app = await NestFactory.create(AppModule); app.useGlobalGuards(new AuthGuard()); await app.listen(3000); } bootstrap();
|
或者通过模块的 providers 注册全局守卫(支持依赖注入):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 {}
|
实战:JWT 认证守卫
借助 @nestjs/jwt 和 @nestjs/passport,我们可以快速实现一个 JWT 认证守卫:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { Injectable, UnauthorizedException } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport';
@Injectable() export class JwtAuthGuard extends AuthGuard('jwt') { handleRequest(err: any, user: any) { if (err || !user) { throw err || new UnauthorizedException('无效的令牌'); } return user; } }
|
1 2 3 4 5 6 7 8 9 10 11 12
| import { Controller, Get, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from './jwt-auth.guard';
@Controller('users') export class UsersController { @Get('profile') @UseGuards(JwtAuthGuard) getProfile() { return { message: '认证通过!' }; } }
|
实战:角色权限守卫
很多时候,我们需要根据用户角色来控制访问权限。
首先创建一个自定义装饰器来传递角色元数据:
1 2 3 4
| import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);
|
然后创建守卫来检查角色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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)); } }
|
使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import { Controller, Post, UseGuards } from '@nestjs/common'; import { JwtAuthGuard } from './jwt-auth.guard'; import { RolesGuard } from './roles.guard'; import { Roles } from './roles.decorator';
@Controller('admin') @UseGuards(JwtAuthGuard, RolesGuard) export class AdminController { @Post('delete-user') @Roles('admin') deleteUser() { return { message: '用户已删除' }; }
@Get('stats') @Roles('admin', 'moderator') getStats() { return { message: '统计信息' }; } }
|
ExecutionContext 的更多用法
除了获取 HTTP 请求,ExecutionContext 还支持其他传输层:
1 2 3 4 5 6 7 8 9 10
| @Injectable() export class WsGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const client = context.switchToWs().getClient(); const data = context.switchToWs().getData(); return !!client.user; } }
|
1 2 3 4 5 6 7 8
| @Injectable() export class RpcGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const data = context.switchToRpc().getData(); return validateRpcRequest(data); } }
|
守卫中的错误处理
当守卫返回 false 时,NestJS 默认抛出 ForbiddenException。你也可以手动抛出任何异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Injectable() export class AuthGuard implements CanActivate { canActivate(context: ExecutionContext): boolean { const request = context.switchToHttp().getRequest(); const token = request.headers.authorization;
if (!token) { throw new UnauthorizedException('缺少认证令牌'); }
try { return true; } catch { throw new ForbiddenException('令牌无效或已过期'); } } }
|
总结
NestJS 的守卫机制提供了一套优雅的权限控制方案:
- 职责单一:守卫只做一件事——决定请求是否放行
- 可组合:多个守卫可以同时使用,按顺序执行
- 可复用:同一个守卫可以应用到不同控制器和路由
- 与反射结合:通过
Reflector 和自定义装饰器实现声明式权限控制
在实际项目中,通常会将 JwtAuthGuard(谁在访问)和 RolesGuard(能不能访问)组合使用,形成完整的认证授权链路。
下次遇到需要权限控制的场景,不妨试试 NestJS 守卫,它会让你的代码更加清晰和可维护。