Пошук уроків, статей та іншого контенту
Застосуєте throttling для захисту endpoint-ів від зловживань, перебору паролів і простих DDoS-атак.
Throttling — це обмеження кількості запитів, які клієнт може виконати за певний проміжок часу.
Наприклад:
не більше 100 запитів за хвилину для звичайних endpoint-ів;
не більше 5 спроб входу за хвилину;
не більше 3 запитів за секунду до дорогого endpoint-а.
Коли клієнт перевищує ліміт, NestJS повертає HTTP-відповідь зі статусом 429 Too Many Requests.
Throttling допомагає:
зменшити кількість автоматизованих запитів;
ускладнити перебір паролів;
захистити ресурсоємні endpoint-и;
зменшити вплив простих DDoS-атак.
Це не заміна повноцінному захисту на рівні reverse proxy, CDN або firewall. Throttling працює всередині застосунку, тому запити вже досягають NestJS.
Встановіть офіційний пакет для NestJS:
npm install @nestjs/throttlerПісля цього підключіть модуль і глобальний guard.
Створімо базове правило: не більше 10 запитів за 60 секунд.
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import {
ThrottlerGuard,
ThrottlerModule,
} from '@nestjs/throttler';
import { AppController } from './app.controller';
@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'default',
ttl: 60_000,
limit: 10,
},
]),
],
controllers: [AppController],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}Тут:
name — назва конфігурації;
ttl — тривалість часового вікна в мілісекундах;
limit — максимальна кількість запитів у цьому вікні;
APP_GUARD робить ThrottlerGuard глобальним для всього застосунку.
Після цього кожен endpoint контролюватиме кількість запитів клієнта.
Наприклад, для конфігурації limit: 10 і ttl: 60_000 одинадцятий запит протягом хвилини отримає відповідь 429.
// app.controller.ts
import {
Controller,
Get,
Post,
Body,
} from '@nestjs/common';
import {
SkipThrottle,
Throttle,
} from '@nestjs/throttler';
@Controller()
export class AppController {
@Get('health')
@SkipThrottle()
health() {
return { status: 'ok' };
}
@Get('posts')
getPosts() {
return {
message: 'Список постів',
};
}
@Post('login')
@Throttle({
default: {
limit: 5,
ttl: 60_000,
},
})
login(@Body() body: { email: string; password: string }) {
return {
message: `Спроба входу для ${body.email}`,
};
}
}У цьому прикладі:
GET /health не має обмеження;
GET /posts використовує глобальний ліміт;
POST /login має окремий ліміт — 5 запитів за хвилину.
Декоратор @Throttle() перевизначає глобальне правило для конкретного endpoint-а.
Endpoint входу зазвичай потребує суворішого ліміту, оскільки саме його найчастіше використовують для перебору паролів.
Перевірити поведінку можна за допомогою curl:
for i in {1..6}; do
curl -i \
-X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"wrong-password"}'
doneПісля п'яти успішних відповідей наступний запит має отримати статус 429 Too Many Requests.
Типово throttler групує запити за IP-адресою клієнта. Це означає, що ліміт застосовується окремо для кожного IP.
Однак у production-застосунку потрібно врахувати reverse proxy або load balancer. Якщо NestJS працює за проксі, він може бачити IP-адресу проксі, а не реального клієнта. У такому разі всі користувачі можуть помилково потрапити під один спільний ліміт.
Також важливо пам'ятати про обмеження за IP:
кілька користувачів за одним NAT можуть ділити один ліміт;
атакер може змінювати IP-адреси;
один користувач може використовувати кілька адрес.
Для endpoint-а входу часто застосовують додаткове обмеження за ідентифікатором облікового запису, наприклад за email. Це складніша політика, яку потрібно реалізувати окремо від базового IP-based throttling.
Для різних типів запитів можна оголосити кілька конфігурацій:
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import {
ThrottlerGuard,
ThrottlerModule,
} from '@nestjs/throttler';
@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'short',
ttl: 1_000,
limit: 3,
},
{
name: 'medium',
ttl: 10_000,
limit: 20,
},
{
name: 'long',
ttl: 60_000,
limit: 100,
},
]),
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
],
})
export class AppModule {}Тепер можна призначити endpoint-у потрібний набір правил:
import { Controller, Get } from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
@Controller('reports')
export class ReportsController {
@Get('generate')
@Throttle({
short: {
limit: 1,
ttl: 1_000,
},
medium: {
limit: 5,
ttl: 10_000,
},
})
generateReport() {
return {
message: 'Звіт генерується',
};
}
}Такий endpoint не можна викликати частіше одного разу за секунду і більше п'яти разів за десять секунд.
Кілька вікон корисні, коли потрібно одночасно контролювати:
короткочасні сплески запитів;
активність протягом кількох секунд;
загальну активність протягом хвилини.
Для endpoint-а, який повинен бути доступним без throttling, використовуйте @SkipThrottle():
import { Controller, Get } from '@nestjs/common';
import { SkipThrottle } from '@nestjs/throttler';
@Controller('health')
export class HealthController {
@Get()
@SkipThrottle()
check() {
return {
status: 'ok',
};
}
}Використовуйте винятки обережно. Якщо вимкнути throttling для endpoint-а, доступного з інтернету, він може стати зручною ціллю для великої кількості запитів.
Зазвичай throttling вимикають для:
health-check endpoint-а;
внутрішнього endpoint-а, доступ до якого вже обмежений мережею;
технічного endpoint-а, який викликається інфраструктурою.
За замовчуванням лічильники зберігаються в пам'яті процесу застосунку.
Це підходить для:
локальної розробки;
простого застосунку з одним процесом;
тестування throttling.
Для кількох екземплярів застосунку таке зберігання має проблему: кожен екземпляр матиме власний лічильник. Клієнт може надсилати запити на різні екземпляри й фактично отримати більший ліміт.
У production-середовищі з кількома інстансами потрібне спільне сховище лічильників, сумісне з обраною конфігурацією throttler-а. Це дозволяє всім екземплярам застосунку використовувати однакові ліміти.
Ліміт має відповідати призначенню endpoint-а.
Орієнтовний підхід:
health-check: без throttling або високий ліміт;
читання публічних даних: помірний ліміт;
пошук: нижчий ліміт, якщо операція дорога;
надсилання email або SMS: дуже суворий ліміт;
login: кілька спроб за хвилину;
password reset: суворий ліміт, щоб не зловживати відправленням листів.
Не варто встановлювати однаковий ліміт для всіх endpoint-ів. Дешевий GET і операція генерації звіту можуть мати дуже різну вартість для сервера.
ttl задається в мілісекундах:
{
ttl: 60_000,
limit: 10,
}Це означає 60 секунд. Значення 60 означає лише 60 мілісекунд, а не 60 секунд.
Самого імпорту ThrottlerModule недостатньо. Потрібно також зареєструвати ThrottlerGuard:
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard,
},
]Без цього правила модуля не застосовуватимуться до endpoint-ів.
Якщо встановити маленький глобальний ліміт, звичайний користувач може отримувати 429 під час нормальної роботи застосунку.
Краще:
встановити розумний загальний ліміт;
для чутливих endpoint-ів призначити окремі суворіші правила;
протестувати поведінку під типовим навантаженням.
IP-адреса не завжди ідентифікує одного користувача. Не покладайтеся лише на неї для захисту login або password reset. Для критичних операцій може знадобитися окрема політика за email, ідентифікатором користувача або комбінацією кількох ознак.
Throttling обмежує обробку запитів на рівні NestJS, але не зупиняє великий обсяг мережевого трафіку до сервера. Для масштабних атак потрібні додаткові інструменти інфраструктури.
Throttling обмежує кількість запитів за часове вікно.
У NestJS для цього використовується пакет @nestjs/throttler.
ThrottlerGuard, зареєстрований через APP_GUARD, застосовує правила глобально.
ttl задається в мілісекундах, а limit визначає максимальну кількість запитів.
@Throttle() дає змогу встановити окремий ліміт для endpoint-а.
@SkipThrottle() вимикає throttling для конкретного endpoint-а.
Для login і password reset варто використовувати суворіші правила.
Вбудоване зберігання в пам'яті не підходить для узгодженого throttling між кількома інстансами.
Throttling зменшує зловживання, але не замінює інфраструктурний захист від масштабних DDoS-атак.