Флуд контроль

Нам нужна система, которая не будет мешать пользоваться сервисом, но будет препятствовать злоупотреблением его функций. Согласитесь, не хорошо, если какой-либо тяжёлый функционал злоумышленник запускает очень и очень много раз в секунду. Последствия – в лучшем случае – падение вашего сервиса. Для начала установим пакеты:

NPM:

npm i -s fastify-rate-limit ioredis

YARN:

yarn add fastify-rate-limit ioredis

Далее нам потребуется Redis. * Redis нужен для синхронизации приложений, запущенных в кластерном режиме. Windows: скачать с GitHub

(далее распаковываем в любую папку и запускаем redis-server.exe)

Linux: советую почитать статью по настройке и установке Далее нам нужно добавить плагин в fastify и произвести connect redis в index.js: *Плагины чувствительны к порядку инициализации, по этой причине самые важные плагины должны быть выше посредственных. На этапе реализации Rate Limit'a я столкнулся с проблемой. Проверять подпись по нескольку раз в Middleware и в Rate Limit хоть и не так ресурсозатратно, но не очень хорошо. А передать данные в body не имеет смысла, так как Raid Limit срабатывает до инициализации body в запросе. Поэтому было принято решение: добавить новый объект sign, который будет содержать все параметры запуска в случае, если они корректны.

***
const sign = require('sign'); // импорт функции проверки подписи.
const Redis = require('ioredis'); // импорт либы для использования redis.
const redis = new Redis({
    connectionName: process.env.REDIS_CONNECTION_NAME,
    host: process.env.REDIS_HOST,
    port: +process.env.REDIS_PORT,
    connectTimeout: +process.env.REDIS_TIMEOUT
})

const fastify = require('fastify')({
    logger: {
        level: 'error',
        file: `log/${new Date().getFullYear()}_${new Date().getMonth()}_${new Date().getDay()}.log`
    }
});
fastify.register(require('fastify-formbody'));

fastify.register(require('fastify-rate-limit'),
    {
        global: false, // Не используем глобальные лимиты.
        redis: redis, // Используем redis для хранения данных о лимитах.
        keyGenerator: (request) => {
            // Проверяем подпись параметров запуска.
            // Параметры передаются в header в параметре Authorization
            let param = sign(request.headers.authorization); 
            if (param) {
                request.sign = param;
                return param.vk_user_id;
            } else { return "undefined" }
        },
        errorResponseBuilder: () => { return undefined },
    });
***

Далее, как вы уже, поняли, нам нужно добавить env параметры (это стандартные параметры подключения, если вы меняли конфигурацию redis, то и тут данные могут быть другими):

***
"env": {
          ***
          "REDIS_CONNECTION_NAME": "RAID_LIMIT",
          "REDIS_HOST": "localhost",
          "REDIS_PORT": "6379",
          "REDIS_TIMEOUT": "500"
          ***
}
***

Теперь наш Флуд контроль готов к бою. Но для того, чтобы он работал, нужно изменить контроллер так, чтобы он определял лимиты запросов одного пользователя к нему:

module.exports = {
    method: ***,
    config: {
        rateLimit: {
            max: 10,
            timeWindow: 5000
        }
    },
    async execute(fastify, request, reply) { *** }
}

Тут всё просто: config.rateLimit.max – количество запросов в промежуток времени, указанный в config.rateLimit.timeLimit; config.rateLimit.timeLimit – промежуток времени в миллисекундах. Пример:

// 10 запросов в 5 секунд
config: {
        rateLimit: {
            max: 10,
            timeWindow: 5000
        }
    }
//

// 10 запросов в час
config: {
        rateLimit: {
            max: 10,
            timeWindow: 1000 * 3600
        }
    }
//

При привышении лимитов пользователь начнёт получать 429 код ошибки, а сервер, со своей стороны, будет игнорировать запросы, не передавая информацию контроллерам. Основа для полноценного флуд-контроля положена. Остальное зависит от ваших целей и идей. К примеру, можно реализовать чёрный список для пользователей, которые очень сильно флудят запросами к бэку. Хранить информацию можно как в Redis, так и в любой базе данных. В своём пакете для маршрутизации я добавил две функции: onExceeding вызывается после каждого запроса, у которого есть флуд контроль; onExceeded вызывается после каждого запроса, у которого превышен лимит. Две эти функции запускаются после того, как выполнение кода контроллера было завершено ответом пользователю. Что позволяет максимально быстро обработать запрос, а уже потом работать над добавлением пользователя в чёрный список или дать ему бан на некоторое время, в целях предотвращения флуда. Логика работы этого функционала на прямую зависит от того, какие технологии вы будете использовать для хранения, поэтому привожу только краткий пример назначения этих функций. Их также можно не указывать, в таком случае никаких действий произведено не будет.

fastify.register(require('routes'), {
    path: "controllers",
    middleware: './module/middleware',
    onExceeding: (req) => { console.log(req)},
    onExceeded: (req) => { console.log(req)}
});

На этом мы заканчиваем с реализацией Флуд контроля.

Last updated

Was this helpful?