Skip to content

NestJS 守卫使用场景

JWT 身份认证、角色授权(RBAC)、细粒度权限控制、多策略认证授权、IP 白名单守卫、接口访问频率限制

JWT 身份认证

验证请求发起者是否为合法用户(如登录态校验),替代传统中间件认证的优势是:守卫可通过 ExecutionContext 精准获取请求、路由、用户等上下文信息,且支持依赖注入(如注入认证服务)。

  1. 定义守卫
ts
// jwt-auth.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { Request } from 'express';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  constructor(private jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // 获取请求对象(支持 HTTP/WSS/RPC 等上下文)
    const request = context.switchToHttp().getRequest<Request>();
    const token = this.extractTokenFromHeader(request);

    if (!token) {
      throw new UnauthorizedException('未提供认证 Token');
    }

    try {
      // 验证 Token 并解析用户信息
      const payload = await this.jwtService.verifyAsync(token, {
        secret: process.env.JWT_SECRET,
      });
      // 将用户信息挂载到请求对象,供后续处理器使用
      request.user = payload;
    } catch {
      throw new UnauthorizedException('Token 无效或已过期');
    }
    return true;
  }

  private extractTokenFromHeader(request: Request): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}
  1. 使用守卫
ts
// 控制器
@Controller('users')
export class UsersController {
  // 局部守卫:仅该接口需要认证
  @Get('profile')
  @UseGuards(JwtAuthGuard)
  getProfile(@Request() req) {
    return req.user;
  }
}

// 全局守卫:所有接口默认需要认证(可通过装饰器排除)
// app.module.ts
providers: [
  {
    provide: APP_GUARD,
    useClass: JwtAuthGuard,
  },
];

角色授权(RBAC)

最常用的场景:限制特定角色(如管理员、普通用户)访问接口,结合自定义装饰器 + 守卫实现。

  1. 定义角色装饰器
ts
// roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles);
  1. 实现角色守卫
ts
// roles.guard.ts
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ROLES_KEY } from './roles.decorator';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // 获取路由上的角色元数据
    const requiredRoles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
      context.getHandler(), // 优先获取方法级元数据
      context.getClass(), // 其次获取控制器级元数据
    ]);

    // 无角色限制时直接放行
    if (!requiredRoles) return true;

    // 获取已认证的用户信息
    const request = context.switchToHttp().getRequest();
    const user = request.user;

    // 校验用户角色是否匹配
    const hasRole = requiredRoles.includes(user.role);
    if (!hasRole) {
      throw new ForbiddenException('无权限访问(需要角色:' + requiredRoles.join(',') + '');
    }
    return hasRole;
  }
}
  1. 使用守卫
ts
@Controller('admin')
export class AdminController {
  // 仅管理员可访问
  @Post('create-user')
  @UseGuards(JwtAuthGuard, RolesGuard) // 先认证,再鉴权
  @Roles('admin')
  createUser() {
    return '创建用户成功';
  }

  // 管理员/运营均可访问
  @Get('logs')
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin', 'operator')
  getLogs() {
    return '操作日志列表';
  }
}

细粒度权限控制

资源所有权 、操作权限

  1. 定义守卫
ts
// 示例:验证文章所有权
// 比角色更细的控制:比如 “仅文章作者可编辑自己的文章”、“用户只能修改自己的密码”、“基于权限码(如 article:edit)控制操作”。

// ownership.guard.ts
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';
import { ArticlesService } from './articles.service';

@Injectable()
export class ArticleOwnershipGuard implements CanActivate {
  constructor(private articlesService: ArticlesService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const userId = request.user.id; // 已认证用户ID
    const articleId = request.params.id; // 要操作的文章ID

    // 校验文章是否属于当前用户
    const article = await this.articlesService.findOne(articleId);
    if (!article || article.authorId !== userId) {
      throw new ForbiddenException('无权操作他人的文章');
    }
    return true;
  }
}
  1. 使用守卫
ts
@Put('articles/:id')
@UseGuards(JwtAuthGuard, ArticleOwnershipGuard)
updateArticle(@Param('id') id: string, @Body() data) {
  return this.articlesService.update(id, data);
}

多策略认证授权

同一接口支持多种认证方式(如 JWT + OAuth2 + API Key),或根据不同条件切换授权逻辑。

ts
// multi-strategy.guard.ts
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ApiKeyService } from './api-key.service';

@Injectable()
export class MultiStrategyGuard implements CanActivate {
  constructor(
    private jwtService: JwtService,
    private apiKeyService: ApiKeyService
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();

    // 尝试 JWT 认证
    try {
      const token = request.headers.authorization?.split(' ')[1];
      if (token) {
        const payload = await this.jwtService.verifyAsync(token);
        request.user = payload;
        return true;
      }
    } catch {}

    // JWT 失败,尝试 API Key 认证
    const apiKey = request.headers['x-api-key'];
    if (apiKey && (await this.apiKeyService.validate(apiKey))) {
      request.user = await this.apiKeyService.getUserByApiKey(apiKey);
      return true;
    }

    // 所有策略失败
    throw new UnauthorizedException('请通过 JWT 或 API Key 认证');
  }
}

IP 白名单守卫

ts
// ip-whitelist.guard.ts
import { CanActivate, ExecutionContext, Injectable, ForbiddenException } from '@nestjs/common';

@Injectable()
export class IpWhitelistGuard implements CanActivate {
  // 白名单 IP 列表(可从配置/数据库读取)
  private readonly whitelist = ['127.0.0.1', '192.168.1.100'];

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const clientIp = request.ip || request.ips[0]; // 获取客户端IP

    if (!this.whitelist.includes(clientIp)) {
      throw new ForbiddenException('IP 不在白名单内,禁止访问');
    }
    return true;
  }
}

接口访问频率限制

ts
// src/guards/rate-limit.guard.ts
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  TooManyRequestsException,
} from '@nestjs/common';

// 模拟存储 IP 访问次数(生产环境用 Redis)
const ipAccessMap = new Map<string, { count: number; lastTime: number }>();

@Injectable()
export class RateLimitGuard implements CanActivate {
  // 限制:1 分钟内最多 10 次请求
  private readonly limit = 10;
  private readonly window = 60 * 1000;

  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    const ip = request.ip;
    const now = Date.now();

    // 初始化/更新 IP 访问记录
    const record = ipAccessMap.get(ip) || { count: 0, lastTime: now };
    if (now - record.lastTime > this.window) {
      record.count = 1;
      record.lastTime = now;
    } else {
      record.count += 1;
    }
    ipAccessMap.set(ip, record);

    // 校验频率
    if (record.count > this.limit) {
      throw new TooManyRequestsException('请求过于频繁,请稍后再试');
    }
    return true;
  }
}