NestJS守卫(Guard) #

什么是守卫? #

守卫是用@Injectable()装饰器装饰的类,它实现了CanActivate接口。守卫决定请求是否应该被路由处理程序处理,主要用于权限控制和认证。

守卫与中间件的区别 #

特性 中间件 守卫
执行时机 路由匹配之前 路由匹配之后
访问ExecutionContext
主要用途 日志、请求处理 权限控制、认证
抛出异常 需要手动处理 自动处理

创建守卫 #

基本守卫 #

typescript
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 this.validateRequest(request);
  }

  private validateRequest(request: any): boolean {
    return !!request.headers.authorization;
  }
}

ExecutionContext #

ExecutionContext提供了请求的上下文信息:

typescript
interface ExecutionContext extends ArgumentsHost {
  getClass<T>(): Type<T>;           // 获取控制器类
  getHandler(): Function;           // 获取处理方法
}

应用守卫 #

方法级别 #

typescript
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';

@Controller('users')
export class UsersController {
  @Get()
  @UseGuards(AuthGuard)
  findAll() {
    return 'Protected route';
  }
}

控制器级别 #

typescript
@Controller('users')
@UseGuards(AuthGuard)
export class UsersController {
  @Get()
  findAll() {}

  @Get('profile')
  getProfile() {}
}

全局级别 #

typescript
const app = await NestFactory.create(AppModule);
app.useGlobalGuards(new AuthGuard());

认证守卫 #

JWT认证守卫 #

typescript
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  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> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);

    if (!token) {
      throw new UnauthorizedException('Token not found');
    }

    try {
      const payload = await this.jwtService.verifyAsync(token);
      request.user = payload;
    } catch {
      throw new UnauthorizedException('Invalid token');
    }

    return true;
  }

  private extractTokenFromHeader(request: Request): string | undefined {
    const [type, token] = request.headers.authorization?.split(' ') ?? [];
    return type === 'Bearer' ? token : undefined;
  }
}

使用认证守卫 #

typescript
@Controller('users')
@UseGuards(JwtAuthGuard)
export class UsersController {
  @Get('profile')
  getProfile(@Request() req) {
    return req.user;
  }
}

角色守卫 #

定义角色 #

typescript
export enum Role {
  USER = 'user',
  ADMIN = 'admin',
  SUPER_ADMIN = 'super_admin',
}

角色装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';
import { Role } from './role.enum';

export const ROLES_KEY = 'roles';
export const Roles = (...roles: Role[]) => SetMetadata(ROLES_KEY, roles);

角色守卫实现 #

typescript
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Role } from './role.enum';
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<Role[]>(ROLES_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (!requiredRoles) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();

    if (!user) {
      throw new ForbiddenException('User not authenticated');
    }

    const hasRole = requiredRoles.some(role => user.role === role);

    if (!hasRole) {
      throw new ForbiddenException('Insufficient permissions');
    }

    return true;
  }
}

使用角色守卫 #

typescript
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ADMIN)
export class AdminController {
  @Get('users')
  findAllUsers() {
    return 'All users';
  }

  @Get('settings')
  @Roles(Role.SUPER_ADMIN)
  getSettings() {
    return 'Admin settings';
  }
}

权限守卫 #

权限装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const PERMISSIONS_KEY = 'permissions';
export const RequirePermissions = (...permissions: string[]) =>
  SetMetadata(PERMISSIONS_KEY, permissions);

权限守卫 #

typescript
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';

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

  canActivate(context: ExecutionContext): boolean {
    const requiredPermissions = this.reflector.getAllAndOverride<string[]>(
      PERMISSIONS_KEY,
      [context.getHandler(), context.getClass()],
    );

    if (!requiredPermissions) {
      return true;
    }

    const { user } = context.switchToHttp().getRequest();

    if (!user || !user.permissions) {
      throw new ForbiddenException('No permissions');
    }

    const hasAllPermissions = requiredPermissions.every(permission =>
      user.permissions.includes(permission),
    );

    if (!hasAllPermissions) {
      throw new ForbiddenException('Insufficient permissions');
    }

    return true;
  }
}

使用权限守卫 #

typescript
@Controller('users')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class UsersController {
  @Get()
  @RequirePermissions('user:read')
  findAll() {
    return 'All users';
  }

  @Post()
  @RequirePermissions('user:create')
  create() {
    return 'User created';
  }

  @Delete(':id')
  @RequirePermissions('user:delete')
  remove() {
    return 'User deleted';
  }
}

公开路由装饰器 #

创建公开装饰器 #

typescript
import { SetMetadata } from '@nestjs/common';

export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);

修改认证守卫 #

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

  canActivate(context: ExecutionContext): boolean {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    if (isPublic) {
      return true;
    }

    // JWT验证逻辑
    return this.validateToken(context);
  }
}

使用公开装饰器 #

typescript
@Controller('auth')
export class AuthController {
  @Post('login')
  @Public()
  login() {
    return 'Public login endpoint';
  }

  @Post('register')
  @Public()
  register() {
    return 'Public register endpoint';
  }

  @Get('profile')
  getProfile() {
    return 'Protected profile endpoint';
  }
}

组合守卫 #

typescript
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard, PermissionsGuard)
export class AdminController {
  @Get('dashboard')
  @Roles(Role.ADMIN)
  @RequirePermissions('dashboard:read')
  getDashboard() {
    return 'Admin dashboard';
  }
}

守卫最佳实践 #

1. 单一职责 #

每个守卫只负责一种验证:

typescript
// 认证守卫只负责认证
@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    // 验证用户身份
  }
}

// 角色守卫只负责角色验证
@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    // 验证用户角色
  }
}

2. 使用装饰器配置 #

typescript
@Controller('users')
@UseGuards(AuthGuard, RolesGuard)
export class UsersController {
  @Get()
  @Roles(Role.ADMIN)
  findAll() {}
}

3. 提供清晰的错误信息 #

typescript
throw new ForbiddenException({
  message: 'Access denied',
  reason: 'User does not have admin role',
  requiredRole: Role.ADMIN,
  currentRole: user.role,
});

总结 #

本章学习了NestJS守卫:

  • 守卫的概念和用途
  • 认证守卫的实现
  • 角色守卫的实现
  • 权限守卫的实现
  • 公开路由装饰器
  • 守卫最佳实践

接下来,让我们学习 拦截器(Interceptor)

最后更新:2026-03-28