# Защитники(Guards)

**Guard** - это класс, аннотированный декоратором `@Injectable()`. **Guards** должны реализовать интерфейс `CanActivate`.

![](https://2756464857-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-M3bEWXTnB7SzDtj31Er%2F-M4kccIDb7IJtNt9qX_t%2F-M4kd9Cw57jycIkGIvDx%2FGuards_1.png?alt=media\&token=7e91b2c0-e97c-4ba9-b849-de5693a4222a)

**Guards** имеют **единственную ответственность**. Они определяют, будет ли данный запрос обрабатываться обработчиком маршрута или нет, в зависимости от определенных условий (таких как права, роли, ACLs и т.д.), представленных во время выполнения. Это часто называют авторизацией. Авторизация (и ее двоюродный брат, аутентификация, с которой она обычно сотрудничает) обычно обрабатывается промежуточным слоем([**middleware**](https://docs.nestjs.com/middleware)) в традиционных Express-приложениях. **Middleware** является прекрасным выбором для аутентификации, поскольку такие вещи, как проверка токена и прикрепление свойств к объекту `request`, не сильно связаны с конкретным контекстом маршрута (и его метаданными).

Но middleware, по своей природе, является тупым. Он не знает, какой обработчик будет выполнен после вызова функции `next()`. С другой стороны, **Guards** имеют доступ к экземпляру `ExecutionContext` и, таким образом, точно знают, что будет выполнено дальше. Они разработаны так же, как фильтры исключений, каналы и перехватчики, чтобы позволить вам вставлять логику обработки в точно нужную точку цикла запроса/ответа и делать это **декларативно**. Это помогает сохранить ваш код декларативным и соответствующим принципам DRY.

{% hint style="info" %}
**Guard** исполняется после каждого middleware, но перед любым перехватчиком(**Interceptor**) или каналом(**Pipe**).
{% endhint %}

### Guard авторизации

Как уже упоминалось, авторизация является отличным вариантом использования для **Guard**, поскольку определенные маршруты должны быть доступны только тогда, когда клиент (обычно конкретный аутентифицированный пользователь) имеет достаточные права. `AuthGuard`, который мы построим сейчас, предполагает аутентифицированного пользователя (и, следовательно, токен прикреплен к заголовкам запроса). Он будет извлекать и проверять токен, а также использовать извлеченную информацию, чтобы определить, может ли запрос продолжаться или нет.

*auth.guard.ts*

```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 validateRequest(request);
  }
}
```

Логика внутри функции `validateRequest()` может быть настолько простой или сложной, насколько это необходимо. Основной смысл этого примера состоит в том, чтобы показать, как **Guard**-ы вписываются в цикл запроса/ответа.

Каждый **Guard** должен реализовать функцию `canActivate()`. Эта функция должна возвращать логическое значение, указывающее, разрешен ли текущий запрос или нет. Он может возвращать ответ либо синхронно, либо асинхронно (через`Promise`или`Observable`). **Nest** использует возвращаемое значение для управления следующим действием:

* если он возвращает значение true, запрос будет обработан.&#x20;
* если он возвращает false, Nest отклонит запрос.

### Контекст исполнения(Execution context)

Функция `canActivate()` принимает один аргумент-экземпляр `ExecutionContext`. `ExecutionContext` наследуется от `ArgumentsHost`. Мы уже видели `ArgumentsHost` ранее в главе фильтры исключений. В приведенном выше примере мы просто используем те же вспомогательные методы, определенные в `ArgumentsHost`, которые мы использовали ранее, чтобы получить ссылку на объект `Request`. Вы можете вернуться к разделу **Arguments host** главы [**фильтры исключений**](https://docs.nestjs.com/exception-filters#arguments-host) для получения дополнительной информации по этому вопросу.

Расширяя `ArgumentsHost`, `ExecutionContext` также добавляет несколько новых вспомогательных методов, которые предоставляют дополнительные сведения о текущем процессе выполнения. Эти сведения могут быть полезны при создании более универсальных **Guards**, которые могут работать с широким набором контроллеров, методов и контекстов выполнения. Узнайте больше о `ExecutionContext` [**здесь**](https://docs.nestjs.com/fundamentals/execution-context).

### Аутентификация на основе ролей

Давайте создадим более функциональную защиту, которая разрешает доступ только пользователям с определенной ролью. Мы начнем с базового шаблона guard и будем опираться на него в следующих разделах. На данный момент он позволяет выполнять все запросы:

*roles.guard.ts*

```typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class RolesGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}
```

### Связывание Guard-а

Как и **Pipes** и фильтры исключений, **Guards** могут быть с областью действия контроллера, с областью действия метода или с глобальной областью действия. Ниже мы настроим защиту с областью действия контроллера с помощью декоратора `@UseGuards()`. Этот декоратор может использовать один аргумент или список аргументов, разделенных запятыми. Это позволяет легко применить соответствующий набор **Guards.**

```typescript
@Controller('cats')
@UseGuards(RolesGuard)
export class CatsController {}
```

{% hint style="info" %}
`@UseGuards()` импортируется из `@nestjs/common`
{% endhint %}

Выше мы передали тип `RolesGuard` (вместо экземпляра), оставив ответственность за создание экземпляра **фреймворку** и включив **инъекцию зависимостей**. Как и в случае с каналами и фильтрами исключений, мы также можем передать экземпляр на месте:

```typescript
@Controller('cats')
@UseGuards(new RolesGuard())
export class CatsController {}
```

Приведенная выше конструкция прикрепляет **Guard** к каждому обработчику, объявленному этим контроллером. Если мы хотим, чтобы **Guard** применялся только к одному методу, мы применяем декоратор `@UseGuards()` на уровне метода.

Для того, чтобы создать глобальный **Guard**, используйте `useGlobalGuards()` метод экземпляра приложения Nest:

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

{% hint style="info" %}
В случае гибридных приложений, использующих метод `useGlobalGuards()`, защита gateways и микросервисов не устанавливается. Для "стандартных" (не гибридных) микросервисных приложений `useGlobalGuards()` действительно монтирует **Guard** глобально.
{% endhint %}

Глобальные **Guard** используются во всем приложении, для каждого контроллера и каждого обработчика маршрута. С точки зрения внедрения зависимостей, глобальные **Guard**, зарегистрированные вне любого модуля (с помощью `useGlobalGuards()`, как в приведенном выше примере), не могут вводить зависимости, поскольку это делается вне контекста любого модуля. Чтобы решить эту проблему, вы можете настроить защиту непосредственно из любого модуля, используя следующую конструкцию:

*app.module.ts*

```typescript
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';

@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: RolesGuard,
    },
  ],
})
export class AppModule {}
```

{% hint style="info" %}
При использовании этого подхода для выполнения инъекции зависимостей для **Guard** обратите внимание, что независимо от модуля, в котором используется эта конструкция, **Guard** фактически является глобальным. Где это должно быть сделано? Выберите модуль, в котором определена защита (`RolesGuard` в приведенном выше примере). Кроме того, `useClass` - это не единственный способ работы с кастомной регистрацией провайдера. Узнайте больше [**здесь**](https://docs.nestjs.com/fundamentals/custom-providers).
{% endhint %}

### Определение ролей для обработчиков

Наша `RolesGuard` работает, но пока не очень умно. Мы еще не воспользовались самой важной функцией защиты - [**контекстом выполнения**](https://docs.nestjs.com/fundamentals/execution-context). Он еще не знает о ролях, или какие роли разрешены для каждого обработчика. Например, у `CatsController` могут быть разные схемы разрешений для разных маршрутов. Некоторые из них могут быть доступны только для администратора, а другие могут быть открыты для всех. Как мы можем сопоставлять роли с маршрутами гибким и многоразовым способом?

Именно здесь в игру вступают кастомные метаданные (подробнее [**здесь**](https://docs.nestjs.com/fundamentals/execution-context#reflection-and-metadata)). **Nest** предоставляет возможность прикреплять пользовательские метаданные к обработчикам маршрутов через декоратор `@SetMetadata()`. Эти метаданные снабжают нас недостающими ролевыми данными, которые необходимы умному **Guard** для принятия решений. Давайте взглянем на использование `@SetMetadata()`:

*cats.controller.ts*

```typescript
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
```

{% hint style="info" %}
Декоратор `@SetMetadata()` импортируется из пакета `@nestjs/common`.
{% endhint %}

С помощью приведенной выше конструкции мы прикрепили метаданные ролей (`roles` - это ключ, а `['admin']` - конкретное значение) к методу `create()`. Хотя это работает, не рекомендуется использовать `@SetMetadata()` непосредственно в ваших маршрутах. Вместо этого создайте свои собственные декораторы, как показано ниже:

*roles.decorator.ts*

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

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

Этот подход гораздо чище и удобочитаемее, а также сильно типизирован. Теперь, когда у нас есть пользовательский декоратор `@Roles()`, мы можем использовать его для оборачивания метода `create()`.

*cats.controller.ts*

```typescript
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

```

### Складываем всё вместе

Давайте теперь вернемся и свяжем это вместе с нашим `RolesGuard`. В настоящее время он просто возвращает `true` во всех случаях, позволяя каждому запросу продолжить работу. Мы хотим сделать возвращаемое значение условным на основе сравнения ролей, назначенных текущему пользователю, с фактическими ролями, требуемыми текущим обрабатываемым маршрутом. Для доступа к роли(ролям) маршрута (пользовательским метаданным) мы будем использовать вспомогательный класс `Reflector`, который присутствует из коробки и предоставляется из пакета `@nestjs/core`.

*roles.guard.ts*

```typescript
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 roles = this.reflector.get<string[]>('roles', context.getHandler());
    if (!roles) {
      return true;
    }
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    return matchRoles(roles, user.roles);
  }
}
```

{% hint style="info" %}
В мире nodeJS, это обычная практика, чтобы прикрепить авторизованного пользователя к объекту `request`. Таким образом, в нашем примере кода выше мы предполагаем, что `request.user` содержит экземпляр пользователя и разрешенные роли. В вашем приложении вы, вероятно, создадите эту ассоциацию в своем кастомном **Guard** аутентификации (или middleware).
{% endhint %}

{% hint style="info" %}
Логика внутри функции `matchRoles()` может быть настолько простой или сложной, насколько это необходимо. Основной смысл этого примера состоит в том, чтобы показать, как **Guard** вписываются в цикл запроса/ответа.
{% endhint %}

Когда пользователь с недостаточными привилегиями запрашивает конечную точку, Nest автоматически возвращает следующий ответ:

```typescript
{
  "statusCode": 403,
  "message": "Forbidden resource",
  "error": "Forbidden"
}
```

Обратите внимание, что за кулисами, когда **Guard** возвращает `false`, фреймворк создает исключение **ForbiddenException**. Если вы хотите вернуть другой ответ на ошибку, вы должны создать свое собственное конкретное исключение. Например:

```typescript
throw new UnauthorizedException();
```

Любое исключение, вызванное **Guard**, будет обрабатываться [**слоем исключений**](https://docs.nestjs.com/exception-filters) (глобальный фильтр исключений и любые фильтры исключений, применяемые к текущему контексту).
