← Назад к вопросам

Что такое Guards в NestJS?

2.3 Middle🔥 132 комментариев
#Безопасность#Фреймворки и библиотеки

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI30 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Guards в NestJS: что это, как использовать, примеры

Guards (охранники) в NestJS — это классы, которые определяют, будет ли обработан тот или иной запрос. Это мощный инструмент для контроля доступа и авторизации.

Основное определение

Guard — это класс, который реализует интерфейс CanActivate и содержит метод canActivate(), который возвращает boolean:

  • true — разрешить обработку запроса
  • false — отклонить запрос

Простейший Guard

// guards/simple.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';

@Injectable()
export class SimpleGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    return true; // Всегда разрешить
  }
}

// Использование на контроллере
import { UseGuards } from '@nestjs/common';

@UseGuards(SimpleGuard)
@Controller('users')
export class UserController {}

Guard для JWT авторизации (самый частый случай)

// guards/jwt-auth.guard.ts
import {
  Injectable,
  UnauthorizedException,
  CanActivate,
  ExecutionContext
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

@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('No token provided');
    }

    try {
      // Верифицируем JWT токен
      const payload = await this.jwtService.verifyAsync(token, {
        secret: process.env.JWT_SECRET
      });
      // Сохраняем payload в request для использования в контроллере
      request.user = payload;
      return true;
    } catch (error) {
      throw new UnauthorizedException('Invalid token');
    }
  }

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

// Использование
@UseGuards(JwtAuthGuard)
@Get('profile')
async getProfile(@Request() req) {
  return req.user; // Получаем payload из guard'а
}

Guard для проверки ролей (Role-based Access Control)

// guards/roles.guard.ts
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException
} 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.get<string[]>(
      'roles',
      context.getHandler()
    );

    // Если роли не установлены - разрешить доступ
    if (!requiredRoles) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const user = request.user;

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

    // Проверяем, есть ли у пользователя одна из требуемых ролей
    const hasRole = requiredRoles.some((role) => user.roles?.includes(role));

    if (!hasRole) {
      throw new ForbiddenException(
        `This action requires one of these roles: ${requiredRoles.join(', ')}`
      );
    }

    return true;
  }
}

// Создаём кастомный декоратор для установки ролей
import { SetMetadata } from '@nestjs/common';

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

// Использование
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles('admin', 'moderator')
@Delete(':id')
async deleteUser(@Param('id') id: string) {
  return this.userService.delete(id);
}

Guard для проверки владельца ресурса

// guards/owner.guard.ts
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  NotFoundException
} from '@nestjs/common';
import { UserService } from '../services/user.service';

@Injectable()
export class OwnerGuard implements CanActivate {
  constructor(private userService: UserService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const { id } = request.params;
    const currentUser = request.user;

    // Получаем ресурс из БД
    const resource = await this.userService.getById(id);

    if (!resource) {
      throw new NotFoundException('Resource not found');
    }

    // Проверяем, является ли текущий пользователь владельцем
    if (resource.userId !== currentUser.id && currentUser.role !== 'admin') {
      throw new ForbiddenException('You can only modify your own resource');
    }

    return true;
  }
}

// Использование
@UseGuards(JwtAuthGuard, OwnerGuard)
@Put(':id')
async updateProfile(@Param('id') id: string, @Body() data: UpdateUserDTO) {
  return this.userService.update(id, data);
}

Guard с асинхронной логикой

// guards/subscription.guard.ts - проверка наличия подписки
import {
  Injectable,
  CanActivate,
  ExecutionContext,
  PaymentRequiredException
} from '@nestjs/common';
import { SubscriptionService } from '../services/subscription.service';

@Injectable()
export class SubscriptionGuard implements CanActivate {
  constructor(private subscriptionService: SubscriptionService) {}

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

    // Асинхронно проверяем подписку в БД
    const subscription = await this.subscriptionService.getUserSubscription(
      user.id
    );

    if (!subscription || subscription.status !== 'active') {
      throw new PaymentRequiredException(
        'You need an active subscription to access this resource'
      );
    }

    // Можем также сохранить данные подписки в request
    request.subscription = subscription;
    return true;
  }
}

// Использование
@UseGuards(JwtAuthGuard, SubscriptionGuard)
@Get('premium-feature')
async getPremiumData(@Request() req) {
  return {
    data: 'premium',
    subscription: req.subscription
  };
}

Комбинирование нескольких Guard'ов

// Все эти guard'ы должны пройти проверку
@UseGuards(JwtAuthGuard, RolesGuard, SubscriptionGuard)
@Roles('admin')
@Post('admin/users')
async createUser(@Body() data: CreateUserDTO) {
  // Здесь пользователь:
  // 1. Аутентифицирован (JwtAuthGuard)
  // 2. Имеет роль 'admin' (RolesGuard)
  // 3. Имеет активную подписку (SubscriptionGuard)
  return this.userService.create(data);
}

Guard с кэшированием результатов

// guards/cached-authorization.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { CacheService } from '../services/cache.service';

@Injectable()
export class CachedAuthorizationGuard implements CanActivate {
  private cache = new Map<string, boolean>();

  constructor(private cacheService: CacheService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const userId = request.user?.id;
    const resource = request.params.id;
    const cacheKey = `${userId}-${resource}`;

    // Проверяем локальный кэш
    const cachedResult = this.cache.get(cacheKey);
    if (cachedResult !== undefined) {
      return cachedResult;
    }

    // Проверяем Redis кэш
    const redisResult = await this.cacheService.get(cacheKey);
    if (redisResult !== null) {
      return redisResult;
    }

    // Выполняем проверку доступа
    const hasAccess = await this.checkAccess(userId, resource);

    // Кэшируем результат на час
    this.cache.set(cacheKey, hasAccess);
    await this.cacheService.set(cacheKey, hasAccess, 3600);

    return hasAccess;
  }

  private async checkAccess(userId: string, resource: string): Promise<boolean> {
    // Реальная проверка доступа
    return true;
  }
}

Guard vs Middleware vs Interceptor

// Guard - проверка доступа
// Когда: перед обработкой контроллера
// Что делает: решает, обрабатывать ли запрос
// Пример: JWT авторизация

// Middleware - предварительная обработка
// Когда: перед guard'ами и контроллером
// Что делает: трансформирует request (добавляет данные)
// Пример: парсинг body

// Interceptor - перехват response'а
// Когда: после контроллера, перед отправкой ответа
// Что делает: трансформирует response, логирует
// Пример: добавление metadata к ответу

// Порядок выполнения:
Middleware -> Guard -> Interceptor (в контроллере) -> Guard -> Interceptor (ответ)

Лучшие практики для Guard'ов

  1. Каждый Guard — одна ответственность

    // ✅ Хорошо
    @UseGuards(JwtAuthGuard, RolesGuard, SubscriptionGuard)
    
    // ❌ Плохо - один большой Guard
    @UseGuards(MonsterGuard) // делает всё
    
  2. Используй Reflector для метаданных

    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    
  3. Кэшируй результаты, если возможно

    // Проверка прав может быть дорогой операцией
    // Кэшируй на несколько минут
    
  4. Выбросй правильные исключения

    throw new UnauthorizedException(); // 401 - не аутентифицирован
    throw new ForbiddenException();    // 403 - нет доступа
    
  5. Тестируй Guard'ы

    const executionContext = {
      switchToHttp: () => ({ getRequest: () => mockRequest })
    };
    const result = await guard.canActivate(executionContext);
    

Выводы

Guards в NestJS — это мощный и удобный способ реализовать:

  • Авторизацию (JWT, OAuth, etc.)
  • Role-based access control (RBAC)
  • Проверку владения ресурсом
  • Проверку подписок
  • Кэширование результатов доступа

Правильное использование Guard'ов делает код более чистым и поддерживаемым.