NestJS 守卫(Guard)使用指南

NestJS 的 守卫(Guard) 是一个非常有用的功能,它主要用于权限验证请求授权。简单来说,守卫决定了一个请求是否应该被路由处理器处理。

守卫是什么?

守卫是一个用 @Injectable() 装饰器注解的类,它实现了 CanActivate 接口。守卫基于 运行时反射 机制工作,在中间件之后、管道/拦截器之前执行。

守卫的核心职责: 返回 truefalse,决定请求是否继续。

  • 返回 true:请求继续执行
  • 返回 false:请求被拒绝,NestJS 自动返回 403 Forbidden

守卫 vs 中间件 vs 管道 vs 拦截器

组件 执行时机 主要用途
中间件 最早 日志、CORS、请求体解析
守卫 中间件之后 鉴权、权限校验
管道 守卫之后 参数校验、类型转换
拦截器 管道之后 响应映射、异常处理

第一个自定义守卫

创建一个简单的认证守卫:

1
2
3
4
5
6
7
8
9
10
11
12
13
// auth.guard.ts
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
// app.controller.ts
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
// main.ts
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
// app.module.ts
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
// jwt-auth.guard.ts
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
// users.controller.ts
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
// roles.decorator.ts
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
// roles.guard.ts
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
// admin.controller.ts
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') // 只有 admin 角色才能访问
deleteUser() {
return { message: '用户已删除' };
}

@Get('stats')
@Roles('admin', 'moderator') // admin 和 moderator 都可以访问
getStats() {
return { message: '统计信息' };
}
}

ExecutionContext 的更多用法

除了获取 HTTP 请求,ExecutionContext 还支持其他传输层:

1
2
3
4
5
6
7
8
9
10
// ws.guard.ts - WebSocket 守卫
@Injectable()
export class WsGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const client = context.switchToWs().getClient();
const data = context.switchToWs().getData();
// 验证 WebSocket 客户端
return !!client.user;
}
}
1
2
3
4
5
6
7
8
// rpc.guard.ts - 微服务守卫
@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 {
// 验证 token ...
return true;
} catch {
throw new ForbiddenException('令牌无效或已过期');
}
}
}

总结

NestJS 的守卫机制提供了一套优雅的权限控制方案:

  1. 职责单一:守卫只做一件事——决定请求是否放行
  2. 可组合:多个守卫可以同时使用,按顺序执行
  3. 可复用:同一个守卫可以应用到不同控制器和路由
  4. 与反射结合:通过 Reflector 和自定义装饰器实现声明式权限控制

在实际项目中,通常会将 JwtAuthGuard(谁在访问)和 RolesGuard(能不能访问)组合使用,形成完整的认证授权链路。

下次遇到需要权限控制的场景,不妨试试 NestJS 守卫,它会让你的代码更加清晰和可维护。