Единая аутентификация на основе Spring Cloud Gateway

Единая аутентификация на основе Spring Cloud Gateway

Инфраструктура динамической маршрутизации и предстоящие вызовы

В предыдущей статье я описал процесс отказа от жёсткой статической структуры конфигурации YAML с использованием Spring Cloud Gateway (далее — SCG) и построения управляемой базой данных архитектуры динамической маршрутизации без простоя. Перенеся ключевые элементы конфигурации инфраструктуры в домен бизнес-данных приложения, мы обеспечили возможность изменять правила маршрутизации в режиме реального времени во время работы системы без её пересборки или повторного развёртывания. В сложной мультивендорной среде это стало мощным буфером, позволившим гибко реагировать на различия в графиках развёртывания у компаний-разработчиков и на изменения конечных точек.

Однако с практической точки зрения создание среды динамической маршрутизации без простоя было лишь первым шагом на пути к гораздо более масштабной цели. Как только мы гибко открыли каналы трафика на уровне инфраструктуры, начали проявляться более фундаментальные и критически важные практические проблемы распределённой микросервисной архитектуры (MSA). В центре внимания оказались две ключевые задачи: поддержание единых стандартов безопасности для десятков разрозненных сервисов и безопасная защита внутренних систем от крупномасштабного трафика, который непредсказуемо возникает в определённые моменты. Речь шла о централизации функций безопасности, управлении трафиком и очередями для обеспечения доступности системы.

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

Интегрированный фильтр аутентификации и авторизации

Первой технической ахиллесовой пятой, с которой сталкиваются при реализации MSA в мультивендорной среде, является согласованность стандартов безопасности. В структуре, где несколько компаний-разработчиков, специализирующихся в разных предметных областях, независимо разрабатывают и развёртывают собственные микросервисы, механизмы аутентификации и авторизации легко фрагментируются внутри отдельных сервисов. В частности, инфраструктура, созданная для этого проекта, работала во внутренней закрытой сети, блокировавшей неконтролируемые контакты с внешними системами. Поэтому было крайне важно иметь мощный Secure Gate, способный единообразно проверять личность каждого входящего запроса и полностью управлять им в соответствии с бизнес-разрешениями, не раскрывая напрямую топологию сети внутренних микросервисов внешним точкам интеграции. Мы реализовали глобальный асинхронный фильтр аутентификации и авторизации на уровне Spring Cloud Gateway, чтобы полностью устранить нагрузку, связанную с безопасностью, позволив каждому сервису сосредоточиться исключительно на своей бизнес-логике и одновременно повысить уровень безопасности всей системы.

Прежде всего, Spring Cloud Gateway внутри использует реактивную архитектуру, основанную на Spring WebFlux и движке Netty. Поэтому вместо традиционного синхронного фильтра на основе модели Servlet, обрабатывающего один запрос в одном потоке, требуется реактивный WebFilter, способный обрабатывать большое количество одновременных подключений в неблокирующем режиме с использованием небольшого числа потоков Event Loop. Реализованный нами интегрированный фильтр аутентификации и авторизации выходит за рамки простой расшифровки JWT (JSON Web Token). Он органично согласует сложные спецификации интеграции между несколькими поставщиками в рамках одного фильтра и выполняет контроль авторизации, сопоставляя запросы в режиме реального времени с разрешёнными для каждого пути API сервисами на основе данных базы. Причина, по которой эта сложная архитектура фильтра смогла стабильно и отзывчиво работать в неблокирующей среде API-шлюза без единой задержки, заключается в трёх ключевых технических механизмах, которые полностью учитывают и корректно применяют особенности реактивных потоков.

Во-вторых, используется механизм динамической замены токенов и адаптации протоколов, обеспечивающий прозрачную поддержку нескольких спецификаций интеграции. Этот фильтр не ограничивается ролью простой точки входа в шлюз, а фактически выполняет функцию централизованного адаптера, гибко согласующего спецификации взаимодействия во внутренней закрытой сети, объединяющей несколько компаний-разработчиков и устаревшие платформы. В мультивендорной среде каждая система часто использует собственную схему учётных данных. Вместо того чтобы заставлять клиента обрабатывать каждый такой случай отдельно, мы спроектировали шлюз так, чтобы он определял назначение запроса и динамически изменял его в режиме реального времени в формат, требуемый нижестоящим сервисом. В частности, при интеграции с устаревшей платформой мы реализовали архитектуру динамической замены токенов, которая проверяет исходный внешний токен, в фоновом режиме динамически получает инфраструктурный токен, необходимый для взаимодействия с внутренним бизнес-доменом, заменяет заголовок новыми учётными данными и пересылает запрос. В результате нам удалось полностью скрыть разнообразные и сложные спецификации аутентификации и авторизации, установленные несколькими компаниями-разработчиками и устаревшими интерфейсами, под слоем шлюза, добившись беспрецедентного снижения связанности между внутренними системами.

В-третьих, используется неблокирующий процесс обработки логов. Процесс сохранения логов (GateLogCdo), необходимых для обеспечения конфиденциальности и безопасности, был предельно точно изолирован, чтобы не влиять отрицательно на скорость ответа основного потока маршрутизации. Вся нагрузка ввода-вывода, связанная с записью аудиторских логов в базу данных и их передачей по сети, делегируется независимому пулу фоновых потоков, полностью отделённому от основного потока выполнения. Это принципиально исключило задержку отдельных запросов из-за сохранения логов и позволило построить архитектуру, полностью сохраняющую ключевые обязанности шлюза — проверку безопасности и посредничество при маршрутизации.

Система управления очередями

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

QueueFilter — точка входа для управления очередью — был реализован через наследование от AbstractGatewayFilterFactory, стандартной спецификации расширения SCG. При каждом запросе этот фильтр извлекает идентификатор маршрута, создавая независимый барьер изоляции трафика, и неблокирующим образом обрабатывает сигналы ожидания, не занимая потоки цикла событий Netty до наступления очереди пользователя.

Ключ к тому, чтобы эта система очередей защищала бэкенд, не исчерпывая собственные ресурсы шлюза, заключается в её архитектурной интеграции, бесшовно соединяющей реактивные потоки со стандартным API конкурентного выполнения Java.

Во-первых, она предоставляет неблокирующий мост между CompletableFuture и реактивными потоками. В традиционной блокирующей структуре реализация очереди требует усыплять потоки, поэтому шлюз выходит из строя первым: потоки исчерпываются пропорционально числу ожидающих пользователей. Чтобы преодолеть эту проблему, мы спроектировали QueueContext так, чтобы для каждой ожидающей сессии создавался пустой CompletableFuture, а сами объекты управлялись в ConcurrentHashMap. Пользователи освобождают выделенный им поток, не занимая ценные потоки цикла событий Netty даже на 1 мс до наступления их очереди. Только после завершения внутренней операции и отправки QueueContext сигнала о завершении очереди реактивный конвейер пробуждается и продолжает выполнение по следующей цепочке маршрутизации, обеспечивая исключительно высокую эффективность использования ресурсов.

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

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

Заключение

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

В конечном счёте архитектурная эволюция заключается не в подгонке бизнеса под ограничения жёсткого оборудования или инфраструктуры. Это процесс гибкого развития самого программного обеспечения, позволяющий ему поддерживать быстро меняющиеся бизнес-требования и сложные структуры сотрудничества. Надеюсь, что созданная нами в этом проекте архитектура динамического управления на основе SCG станет полезным ориентиром для других разработчиков, рассматривающих применение MSA в мультивендорных средах и стремящихся решить проблемы фрагментированной распределённой безопасности и управления крупномасштабным трафиком.

Hustle Paul

Site footer