NestJS 守卫
基础语法
守卫是被 @Injectable() 装饰的类,必须实现 CanActivate 接口(唯一方法 canActivate),其核心职责:
- 接收请求上下文,判断是否允许访问目标路由;
- 返回 boolean
- true:允许访问,请求继续向下执行(管道→拦截器→路由处理器);
- false:拒绝访问,Nest 自动抛出 ForbiddenException(403 状态码)。
ts
// src/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable() // 必须标记为可注入,支持依赖注入
export class RolesGuard implements CanActivate {
// 核心方法:决定是否放行请求
canActivate(context: ExecutionContext): boolean {
// 1. 获取HTTP请求上下文
const request = context.switchToHttp().getRequest();
// 2. 模拟授权逻辑:检查用户是否登录
const isLoggedIn = !!request.user;
return isLoggedIn;
}
}参数说明
ExecutionContext:上下文对象,封装了当前请求的所有信息(HTTP/RPC/WebSocket 通用)
shell
方法 作用 示例(HTTP 上下文)
getClass() 获取当前控制器类 context.getClass() → AppController
getHandler() 获取当前路由处理方法 context.getHandler() → getUser 方法
switchToHttp() 切换到 HTTP 上下文(返回 HttpArgumentsHost) const req = context.switchToHttp().getRequest()
switchToRpc() 切换到 RPC 上下文(微服务) -
switchToWs() 切换到 WebSocket 上下文 -
getType() 获取上下文类型(http/rpc/ws) context.getType() → http
getArgs() 获取路由处理器的参数数组 -多个守卫的执行顺序
@UseGuards() 可传入多个守卫,执行顺序为从左到右,任一守卫返回 false 则终止请求
ts
// 先认证(AuthGuard),再鉴权(RolesGuard)
@Get('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin')
adminOnly() {
return '仅管理员可访问';
}异步守卫
canActivate 支持返回 Promise,适用于需要异步操作(如数据库查询)的授权逻辑
ts
// src/guards/permission.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { PermissionService } from '../services/permission.service';
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly permissionService: PermissionService // 注入权限服务
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
// 1. 读取路由需要的权限
const requiredPerms = this.reflector.get<string[]>('permissions', context.getHandler());
if (!requiredPerms) return true;
// 2. 获取当前用户
const { user } = context.switchToHttp().getRequest();
// 3. 异步查询用户权限(数据库操作)
const userPerms = await this.permissionService.getUserPermissions(user.id);
// 4. 校验权限
return requiredPerms.every((perm) => userPerms.includes(perm));
}
}自定义异常
默认返回 403 Forbidden,可主动抛出自定义异常
ts
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<string[]>('roles', context.getHandler());
const { user } = context.switchToHttp().getRequest();
if (!requiredRoles.includes(user.role)) {
// 自定义异常消息和状态码
throw new ForbiddenException(`需要角色:${requiredRoles.join(', ')},当前角色:${user.role}`);
// 或通用HTTP异常
// throw new HttpException('无权访问', HttpStatus.FORBIDDEN);
}
return true;
}基础守卫
IP 白名单守卫
- 定义守卫
ts
// src/guards/ip-whitelist.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
@Injectable()
export class IpWhitelistGuard implements CanActivate {
// 白名单 IP(可从配置文件注入)
private readonly whitelistIps = ['127.0.0.1', '192.168.1.100'];
canActivate(context: ExecutionContext): boolean {
// 解析 HTTP 请求上下文
const httpContext = context.switchToHttp();
const request = httpContext.getRequest();
// 获取客户端 IP(不同环境可能需要调整,如 X-Forwarded-For)
const clientIp = request.ip || request.ips[0] || request.connection.remoteAddress;
// 校验 IP
const isAllowed = this.whitelistIps.includes(clientIp);
if (!isAllowed) {
// 自定义异常(替代默认的 403)
throw new ForbiddenException(`IP ${clientIp} 无访问权限`);
}
return isAllowed;
}
}- 使用守卫
ts
// src/app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { IpWhitelistGuard } from './guards/ip-whitelist.guard';
@Controller()
export class AppController {
// 路由级别守卫
@Get('protected')
@UseGuards(IpWhitelistGuard)
getProtectedData() {
return { message: '仅白名单 IP 可访问' };
}
}守卫结合元数据
守卫常需结合路由元数据(如 “该路由需要管理员角色”),Nest 提供 Reflector 类用于读取 / 设置元数据,
- 自定义元数据装饰器:避免直接使用 @SetMetadata,封装为语义化装饰器
ts
// src/decorators/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';
// 定义元数据 key 和装饰器
export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);- 实现角色守卫:在守卫中读取元数据
ts
// Reflector 核心方法
// 方法 作用
// getAllAndOverride 优先取路由级别元数据,无则取控制器级别
// getAllAndMerge 合并路由 + 控制器级别元数据(如数组拼接)
// get 读取单个目标(如仅路由)的元数据
// src/guards/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from '../decorators/roles.decorator';
@Injectable()
export class RolesGuard implements CanActivate {
// 注入 Reflector 用于读取元数据
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 1. 读取路由上的 @Roles 元数据(优先级:路由 → 控制器 → 全局)
const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(), // 路由方法的元数据
context.getClass(), // 控制器的元数据
]);
// 2. 无角色要求 → 直接放行
if (!requiredRoles || requiredRoles.length === 0) {
return true;
}
// 3. 解析请求中的用户(假设 JWT 中间件已将用户信息挂载到 req.user)
const request = context.switchToHttp().getRequest();
const user = request.user; // 格式:{ id: 1, roles: ['admin'] }
// 4. 校验用户角色
const hasRequiredRole = user?.roles?.some((role) => requiredRoles.includes(role));
if (!hasRequiredRole) {
throw new ForbiddenException(
`需要角色:${requiredRoles.join(', ')},当前角色:${user?.roles?.join(', ') || '无'}`
);
}
return true;
}
}- 使用角色守卫
ts
// src/app.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common';
import { RolesGuard } from './guards/roles.guard';
import { Roles } from './decorators/roles.decorator';
@Controller()
export class AppController {
// 需 admin 角色才能访问
@Get('admin')
@UseGuards(RolesGuard)
@Roles('admin')
getAdminData() {
return { message: '管理员专属数据' };
}
// 需 editor 或 admin 角色
@Get('editor')
@UseGuards(RolesGuard)
@Roles('editor', 'admin')
getEditorData() {
return { message: '编辑/管理员专属数据' };
}
}