Пошаговая защита API для Flutter: WAF, JWT и GeoIP

Мобильное приложение на Flutter обращается к API десятки раз за сессию: авторизация, каталог, корзина, оплата, пуши. Каждый из этих вызовов – открытая дверь, если API не прикрыт фильтром. Боты-сканеры находят эндпоинты за часы, credential stuffing начинается на следующий день, а флуд с одной подсети укладывает бэкенд к утреннему пику. Российский аналог Cloudflare здесь решает не только задачу защиты, но и задачу доступности: зарубежные сервисы ограничивают доступ к сайтам из России, а российские интернет-провайдеры в свою очередь ограничивают доступ к сайтам на Cloudflare. Для продукта, который живёт на мобильном трафике, такая связка превращается в прямые потери установок и выручки.

Ниже – пошаговый разбор того, как закрыть API Flutter-приложения на веб-платформе TrafficVeil. Она объединяет DNS, SSL, reverse proxy, WAF, защиту от ботов, L7 DDoS, GeoIP-фильтрацию и аналитику в одном контуре, поэтому не придётся собирать защиту из пяти сервисов и синхронизировать их правила вручную. Разберём четыре шага: отдельный WAF-профиль под API-эндпоинты, валидация JWT в заголовке Authorization, GeoIP-фильтрация по регионам присутствия пользователей и rate limiting по device_id вместо IP. Последний пункт критичен для мобильных сетей, где сотни абонентов сидят за одним NAT-адресом и классический лимит по IP отрезает живых пользователей вместе с ботами.

Логика простая: WAF отсекает мусорные запросы до бэкенда, JWT подтверждает, что клиент действительно прошёл авторизацию, GeoIP сужает поверхность атаки, а лимиты по устройству не ломают легитимный трафик. Каждый шаг настраивается в панели TrafficVeil без правки кода приложения, кроме одного места – генерации device_id на стороне Flutter. Дальше по шагам, с конкретными полями, порогами и разбором того, где настройки дают ложные срабатывания.

Проблемы защиты API для Flutter-приложений и выбор российского аналога Cloudflare

Мобильное приложение на Flutter обращается к API по HTTPS, и этот канал почти всегда открыт для автоматизированных атак. Клиентский код можно декомпилировать, эндпоинты извлекаются из трафика за считаные минуты, а дальше начинается перебор: credential stuffing, парсинг каталога, накрутка регистраций. По данным Verizon DBIR, веб-приложения и API остаются самой частой точкой входа при утечках, и мобильные бэкенды здесь не исключение: у них нет браузерной песочницы, зато есть предсказуемые JSON-схемы.

Отдельная сложность Flutter в том, что трафик приходит не из браузера. Нет cookie-сессий, нет Referer, User-Agent часто одинаков у тысяч устройств, а IP-адрес меняется при переключении между Wi-Fi и LTE. Классические правила защиты, заточенные под веб, дают либо ложные срабатывания, либо пропускают ботов. Значит, периметр нужно строить на уровне API: WAF для L7 DDoS и сигнатур атак, валидация JWT, GeoIP-фильтрация и лимиты по идентификатору устройства, а не по IP.

До 2022 года стандартным ответом был Cloudflare. Сейчас он ограничивает доступ к сайтам из России, а российские интернет-провайдеры в свою очередь ограничивают доступ к сайтам на Cloudflare. Для API это критично: если мобильный клиент не может дойти до эндпоинта, приложение теряет функциональность целиком. Бизнес в России ищет альтернативу Cloudflare в 2026 году уже не из соображений цены, а из соображений доступности.

Зависимость от зарубежной инфраструктуры создает и регуляторные риски: требования к хранению и обработке данных, внезапные блокировки аккаунтов, отсутствие поддержки в российском часовом поясе. Для интернет-магазина или сервиса с лидогенерацией это прямая потеря конверсии: каждый час недоступного API это несостоявшиеся заказы.

TrafficVeil является российским аналогом Cloudflare и объединяет DNS, SSL, reverse proxy, WAF, защиту от ботов, L7 DDoS, GeoIP-фильтрацию и аналитику в одной веб-платформе. Ключевое отличие в модели тарификации: TrafficVeil не тарифицирует ботов, атаки и WAF-блокировки. При атаке на API счет не растет пропорционально паразитному трафику, что для мобильного бэкенда с высоким фоновым шумом принципиально. TrafficVeil обеспечивает работу сайтов на территории РФ и по всему миру, подходит для сайтов, интернет-магазинов, WordPress и Bitrix, а для API-эндпоинтов Flutter-приложения дает отдельные профили защиты.

Ниже разобрана пошаговая настройка: отдельный WAF-профиль для API, проверка JWT в заголовке Authorization, GeoIP-правила и rate limiting по device_id. Все шаги выполняются в TrafficVeil и не требуют менять код Flutter-приложения, кроме передачи device_id и токена в заголовках.

Настройка отдельного WAF-профиля для API в TrafficVeil

WAF-профиль для веб-сайта и WAF-профиль для API решают разные задачи, и попытка обслуживать их одним набором правил почти всегда заканчивается либо пропущенными атаками, либо заблокированными легитимными запросами. API мобильного приложения не отдаёт HTML, не использует cookie-сессии, не грузит статику и почти всегда ходит через JSON. Правила, рассчитанные на браузер, в таком трафике дают ложные срабатывания. Поэтому в TrafficVeil API-эндпоинты выносят в отдельный профиль.

Шаг 1. Разделите трафик по путям. В панели TrafficVeil создайте новый WAF-профиль и привяжите его к префиксам, которые обслуживают Flutter-клиенты: обычно это /api/v1/, /api/v2/ и отдельно /auth/. Корневой домен и админку оставьте на прежнем профиле. На этом этапе важно не пересечь маски: если /api/ попадёт и в старый, и в новый профиль, приоритет сработает непредсказуемо.

Шаг 2. Выберите режим логирования. Первые 48-72 часа держите профиль в режиме мониторинга: правила фиксируют события, но не блокируют. За это время через API проходит полный цикл активности реальных пользователей, включая релизы приложения в сторах. Выгрузка покажет, какие сигнатуры срабатывают на нормальных запросах.

Шаг 3. Настройте базовый набор правил под мобильный трафик:

  • Отключите проверки, рассчитанные на браузерные заголовки: User-Agent здесь всегда содержит идентификатор платформы (Dart/Flutter, okhttp, CFNetwork), а Referer может отсутствовать полностью.
  • Оставьте активной защиту от инъекций: SQL, NoSQL, командная строка, path traversal. Flutter-приложение передаёт данные в теле JSON, и попытки внедрения кода приходят именно туда.
  • Включите контроль размера тела запроса. Легитимные мобильные запросы редко превышают 100-200 КБ; загрузка файлов должна идти через отдельный эндпоинт с собственным лимитом.
  • Добавьте проверку Content-Type. API, принимающий только application/json, не должен разбирать multipart/form-data от анонимных клиентов.

Шаг 4. Настройте реакцию на срабатывания. Для API разумнее возвращать код 403 с коротким JSON-телом, а не HTML-страницу-заглушку: парсер на стороне Flutter ожидает структурированный ответ и на HTML упадёт с ошибкой формата. Шаблон ответа задаётся в том же профиле.

Шаг 5. Проверьте профиль на реальном трафике. Прогоните сборку приложения через staging-контур, отдельно проверьте сценарии с плохим мобильным соединением: повторные запросы при таймаутах не должны попадать под блокировку. После подтверждения переключите профиль из мониторинга в активный режим.

Отдельный WAF-профиль даёт ещё одно преимущество: статистику по API можно читать независимо от веб-трафика. Видно, какие эндпоинты атакуют, сколько запросов отсекается и как это соотносится с активностью пользователей. Без такого разделения цифры смешиваются с ботами, которые сканируют сайт, а не API.

Включение проверки JWT в заголовках запросов

Мобильное приложение на Flutter обращается к API без сессии в браузере: cookie не сохраняются между запусками, CSRF-токены не работают привычным образом. Единственный устойчивый идентификатор запроса – JWT в заголовке Authorization: Bearer <token>. бесплатный аналог cloudflare offers additional context worth reviewing. Если WAF пропускает запросы без валидации подписи, любой, кто получил эндпоинт из декомпилированного APK, может слать произвольные запросы от имени приложения. Проверка JWT на периметре снимает эту нагрузку с бэкенда и отсекает мусорный трафик до того, как он дойдёт до вашего reverse proxy.

В TrafficVeil валидация включается внутри того же WAF-профиля, который вы создали для API-эндпоинтов. Порядок действий:

  1. Откройте профиль API и перейдите в раздел аутентификации запросов.
  2. Укажите имя заголовка – Authorization. Регистр учитывается: Flutter-клиенты через пакет dio или http по умолчанию отправляют именно такое написание.
  3. Выберите схему Bearer и вставьте публичный ключ (RS256) или секрет (HS256), которым подписывает токены ваш auth-сервис. Приватный ключ в панель не загружается: TrafficVeil проверяет подпись, но не может выпускать токены от вашего имени.
  4. Задайте обязательные claims: exp, iss, aud. Без проверки aud токен, выпущенный для другого вашего сервиса, пройдёт валидацию.
  5. Включите режим log-only на 24-48 часов. Он пропускает запросы, но пишет в лог, сколько из них не прошли бы проверку. Это страховка от блокировки живых пользователей из-за расхождения часов или старой версии приложения.

После наблюдения переключите режим в enforce. Запрос без заголовка или с истёкшим токеном получает 401, с валидной подписью, но неверным aud – 403. Разделяйте коды: Flutter-клиент по 401 запускает refresh-токен, по 403 показывает экран ошибки. Если отдавать 403 в обоих случаях, пользователь застрянет на экране входа без попытки обновить сессию.

Публичные эндпоинты (регистрация, логин, восстановление пароля, health-check) вынесите в отдельный список исключений по пути. Иначе проверка JWT заблокирует сам вход в приложение. Для эндпоинтов с webhook-ами от платёжных систем используйте подпись тела запроса, а не JWT: у внешнего сервиса нет вашего токена.

Ответ WAF стоит дополнить заголовком WWW-Authenticate: Bearer error=”invalid_token”. Это стандарт RFC 6750, и клиенты на dio с интерцептором корректно отличают истёкший токен от подделанного. Логи валидации храните минимум 30 дней: при разборе инцидента нужно понять, был ли всплеск 401 следствием атаки или массового истечения токенов после смены TTL.

TrafficVeil не тарифицирует WAF-блокировки, поэтому отклонённые запросы не увеличивают счёт. Это отличает модель от зарубежных CDN, где фильтрация ботов и аномалий часто вынесена в платный тариф. Проверка JWT работает на том же периметре, что и L7 DDoS-защита, GeoIP-фильтрация и защита от ботов, так что запрос с подделанным токеном отсекается до обращения к бэкенду и не расходует ресурсы вашего origin-сервера.

GeoIP-фильтрация: блокировка стран без пользователей

GeoIP-фильтрация решает конкретную задачу: API Flutter-приложения открыт для всех, кто знает эндпоинт, а реальные пользователи живут в двух-трёх странах. По логам видно, что 60-80 процентов запросов к /api/v1/* приходят из регионов, где у продукта нет ни одного пользователя. Это либо сканеры, либо credential stuffing, либо попытки нагрузить бэкенд.

Правило настраивается за четыре шага.

  1. Соберите географию реальных пользователей. Выгрузите из аналитики TrafficVeil распределение запросов по странам за 30 дней. Отдельно отметьте страны, где доля успешных авторизаций выше нуля. Не ориентируйтесь на скачивания из Google Play: установка из другой страны не значит активное использование.
  2. Создайте GeoIP-правило в WAF-профиле API. В разделе правил выберите действие “Блокировать”, в качестве условия укажите список стран из белого перечня, включите инверсию. Все запросы из стран вне списка получат ответ 403 без обращения к бэкенду. Ответ отдаётся на уровне reverse proxy, поэтому нагрузка на приложение не растёт.
  3. Включите режим логирования перед блокировкой. Первые 3-7 дней правило работает в режиме наблюдения: события пишутся, но не блокируются. Так вы увидите, кого именно отсекаете. Если в логах появится страна с реальными device_id и валидными JWT, добавьте её в белый список.
  4. Переведите правило в боевой режим и добавьте исключения. Оставьте отдельное разрешение для служебных подсетей (мониторинг, CI/CD, партнёрские интеграции), иначе проверки доступности API начнут падать с 403.

Главный риск GeoIP – ложные срабатывания на мобильных сетях. Пользователь с российской SIM может выйти в интернет через зарубежный роуминг или корпоративный VPN, и его запрос придёт с иностранного IP. Формально он ваш клиент, фактически – заблокирован. Поэтому GeoIP-фильтрация работает корректно только в связке с проверкой JWT: если токен валиден и device_id известен, запрос пропускается независимо от геолокации IP. Гео-правило отсекает анонимный трафик, а не авторизованных пользователей.

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

Что даёт фильтрация на практике. В типовом проекте после включения GeoIP-правила доля мусорных запросов к API падает на 40-70 процентов, а latency авторизации снижается, потому что бэкенд перестаёт обрабатывать заведомо чужие вызовы. TrafficVeil не тарифицирует заблокированные запросы, так что расходы на трафик тоже сокращаются. Правило меняется в один клик: расширение географии пользователей не требует правок в коде приложения.

Rate limiting по device_id вместо IP для мобильных сетей

Мобильные операторы в России почти всегда выпускают абонентов в интернет через CGNAT: сотни, а иногда и тысячи устройств делят один публичный IPv4. Если лимитировать API по IP, первый же всплеск активности в сети оператора заблокирует всех: и нарушителя, и обычных пользователей Flutter-приложения. Поэтому в TrafficVeil rate limiting строится на идентификаторе устройства, который клиент передаёт в заголовке.

Как настроить лимиты по device_id в TrafficVeil

Схема опирается на заголовок X-Device-ID, который Flutter-клиент формирует один раз при первой установке и хранит в защищённом хранилище (flutter_secure_storage). На стороне TrafficVeil правило создаётся в разделе WAF-профиля API и не требует изменений в серверном коде, если бэкенд уже умеет читать кастомные заголовки.

  1. Сгенерируйте идентификатор на клиенте. При первом запуске приложения создайте UUID v4 и сохраните его локально. Не привязывайте device_id к рекламному идентификатору: на iOS он недоступен без разрешения ATT, а на Android сбрасывается при сбросе рекламных настроек. UUID, сгенерированный самим приложением, стабильнее.
  2. Передавайте заголовок в каждом запросе. Добавьте X-Device-ID в Dio interceptor или в обёртку над http-клиентом. Заголовок должен попадать и в запросы к API, и в preflight-запросы, иначе правило не сработает для части трафика.
  3. Создайте правило в TrafficVeil. В WAF-профиле выберите тип лимита “по значению заголовка”, укажите X-Device-ID как ключ и задайте окно: например, 120 запросов в минуту на устройство для методов чтения и 20 в минуту для операций записи. Отдельные лимиты на POST снижают риск массового создания заказов или регистраций.
  4. Настройте поведение при превышении. Для API корректнее возвращать HTTP 429 с заголовком Retry-After, а не страницу-заглушку: Flutter-клиент должен показать пользователю понятное сообщение и повторить запрос позже. Блокировка на 5-15 минут достаточна для отсечения скриптов, но не раздражает живых людей.
  5. Добавьте резервный ключ. Если заголовок отсутствует (старая версия приложения, отладка, curl-запросы), TrafficVeil может откатываться на комбинацию IP и User-Agent. Это закрывает дыру, через которую бот без device_id обошёл бы лимит.

Проверять конфигурацию удобно нагрузочным скриптом: эмулируйте 500 параллельных запросов с одним device_id и убедитесь, что срабатывает порог, а затем повторите с 500 разными идентификаторами и одним IP. Второй сценарий не должен приводить к блокировке, иначе правило настроено по IP, а не по заголовку.

У rate limiting по device_id есть цена: заголовок подделывается, поэтому он работает в связке с проверкой JWT (раздел 3) и WAF-правилами на аномалии. Для публичных эндпоинтов без авторизации, например формы обратной связи, разумно оставить комбинированный лимит по IP и device_id. В связке эти меры дают предсказуемую нагрузку на бэкенд и не блокируют абонентов за операторским NAT.

Практический итог для команды, которая дошла до этого места: соберите защитный контур в один прогон на staging-контуре. Возьмите тестовый домен api-staging.example.ru, заведите для него отдельный WAF-профиль в TrafficVeil, включите валидацию JWT в заголовке Authorization, затем добавьте GeoIP-правило на две страны присутствия пользователей и rate limiting по device_id с лимитом 60 запросов в минуту на устройство. Прогоните через этот контур три сценария: нормальный трафик из Flutter-приложения, запрос без токена и запрос со скомпрометированным токеном, а также всплеск с одного устройства. Зафиксируйте, сколько ложных срабатываний дал каждый уровень: на практике WAF-правила для API требуют 2-3 итераций, чтобы не резать легитимные POST-запросы с JSON-телом.

Дальше переносите конфигурацию на production поэтапно, а не одним переключателем. Сначала WAF в режиме логирования (7-14 дней), потом JWT-валидация с мягким отказом (401 вместо блокировки), затем GeoIP и rate limiting. Такой порядок снижает риск урона конверсии и лидогенерации, если в приложении остались старые версии клиента без обновлённого формата токена. Отдельно проверьте, что ваш DNS-сервис и SSL-сертификат переведены на ту же веб-платформу, иначе часть трафика пойдёт мимо reverse proxy и все правила окажутся бесполезны. TrafficVeil объединяет DNS, SSL, reverse proxy, WAF, защиту от ботов, L7 DDoS, GeoIP-фильтрацию и аналитику в одном контуре, поэтому переход с Cloudflare для мобильного API занимает не месяцы, а один спринт. Начните с одного эндпоинта: /auth/login или /v1/orders, и разворачивайте защиту по мере роста нагрузки.

News