Распределенные системы (aka микросервисы) набрали популярность и применяются все шире в современных реалиях. Сервисов становится больше, привычные задачи для них решаются сложнее, усложнились и вопросы аутентификации и контроля доступа.

В статье рассмотрим различные подходы использования API Gateway как части более общего API security-решения в контексте его работы с токенами доступа, выделяя преимущества, недостатки и связанные с ними вопросы безопасности. Также разберем, почему нужно ограничивать область действия access token и может ли API Gateway помочь и в данном вопросе.

Статья написана на основе материала, с которым выступал на PHDays 2025 и CodeFest 15.

Оглавление

  1. Введение

  2. Аутентификация внешних запросов (North-South)

  3. Использование API Gateway

    1. API security architecture

  4. Работа с токенами доступа при использовании API Gateway

    1. Непрозрачный (opaque) access token и интроспекция на каждом компоненте

    2. Непрозрачный (opaque) access token, интроспекция на API Gateway и распространение контекста в виде структурированных данных без подписи

    3. Access token всегда в виде JWT

      1. Презумпция валидности токена

    4. Два вида access token: reference (opaque) и self-contained (JWT)

      1. Особенности реализации

  5. Безопасность access token

    1. Подходы и направления

    2. Resource Indicators for OAuth 2.0

    3. Развитие оригинальной идеи

    4. Одного API Gateway недостаточно

    5. Варианты оптимизации

  6. Заключение

  7. Литература

Введение

Итак, как отметили, вопросы аутентификации и контроля доступа теперь решаются сложнее: это больше не всегда «создай сессию и выдай session ID → принимай его в запросах». Говоря о запросах, также важно отметить, что к разным запросам или же к разным операциям у нас бывают разные требования в части информационной безопасности, и с этим тоже нужно уметь работать.

И подходы к работе со всеми этими задачами есть. Однако все они так или иначе имеют свои преимущества и недостатки, осознавать которые полезно. Также полезно осознавать и риски, которые становятся актуальными с применением конкретного подхода. Ведь чтобы понять как работать с риском, нужно как минимум знать, что он есть. Неучет рисков приводит к неожиданным проблемам и уязвимым местам, к которым запросто можно оказаться неготовыми.

Введем допущение: здесь и далее будем подразумевать, что для получения токенов используется OAuth 2.0. Отдельно не выделяем OIDC, поскольку он является надстройкой над OAuth 2.0, позволяющей приложению (Relying party) стандартными способами достоверно узнать личность (Identity) пользователя у Identity Provider. Эту часть в статье затрагивать не будем, больше сфокусируемся на функциональности самого OAuth 2.0, которая лежит в в том числе и под OIDC. Также в статье явно не будем подробно разбирать ситуации, где на стороне Client используется Backend‑for‑Frontend (BFF).

Почему именно OAuth 2.0? Его использование нужно далеко не всем, и это факт. Вопрос применимости OAuth 2.0 довольно хорошо освещен в статье Why you probably do not need OAuth2 / OpenID Connect из блога Ory, к которой можно обратиться за более подробным его рассмотрением. Вообще сам фреймворк по задумке был создан для делегирования доступа (в том числе в виде access token). И в контексте используемых схем это пригодится, в частности будет полезно понимание, кому access token был выдан и для кого в конечном счете предназначается. На примере основных терминов OAuth 2.0 сможем рассмотреть подходы на распространенных решениях и не уходить в частные отдельные реализации, коих может быть бесконечное множество. Кроме этого, справедливо будет сказать, что в распределенных системах тот же OAuth 2.0 с его понятиями «Resource», «Resource Owner», «Client», неплохо ложится на их архитектуру. Особенно, когда у нас начинают появляться множественные clients и resources.

Аутентификация внешних запросов (North-South)

Рассмотрим аутентификацию внешних по отношению к системе запросов, или же то, что принято называть «North‑South traffic».

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

Использование API Gateway

Важной составляющей примеров будет применение API Gateway. Существуют различные причины для использования или неиспользования паттерна в конкретных архитектурах, однако в контексте текущей статьи предлагаю выделить наиболее релевантные для нас особенности.

  1. Разделение периметров: сервисы не выставляются для внешнего мира напрямую.
    Да, для современных систем правильнее было фокусироваться уже на подходе Zero Trust, нежели чем на предшествующем ему Perimeter Security. Однако, на мой взгляд, имеет смысл и попытка «взять лучшее из двух миров»: не пренебрегая принципами Zero Trust, предотвратить таким образом возможность прямого вызова внутренних сервисов, обеспечивая тем самым дополнительную линию для defence‑in‑depth. Причем, что такое «внешний мир», может определяться совсем по‑разному, потому такой подход применим и к работе во внутренних сетях.

  2. Публикация API с явным описанием контрактов.
    Применение API Gateway очень хорошо ложится на существующие практики API Management: таким образом возможно выстроить автоматизированный процесс проверок разрабатываемых API и их доставку (читай — публикацию) на конкретные API Gateways. Это позволяет улучшить подходы к разработке API в целом за счет, например, обязательности учета способов аутентификации и проверки авторизации для требуемых групп интерфейсов.

  3. Аутентификация запроса и «грубая» (coarse) проверка его авторизации.
    API Gateway является хорошей точкой для выполнения аутентификации запросов, поскольку все запросы так или иначе будут проходить через него. Возможность проверки токенов и на нем важна и полезна, так как позволяет на раннем этапе отсекать неаутентифицированные запросы, не доводя их до конечных сервисов. Что касается проверки авторизации, предпочтителен подход, где Policy Enforcement Point (PEP), то есть точка, где авторизация проверяется, должна располагаться как можно ближе к конечному сервису, чтобы предотвратить возможность ее обхода. Но главная прелесть состоит в том, что совершенно не обязательно иметь только одну PEP. Применяя начальную проверку авторизацию на уровне самих API, мы также можем значительно снизить количество «невалидных» запросов, достигающих сервисов, а главное, обеспечить консистентность проверок как некую подстраховку, предлагая таким образом еще один слой для обеспечения безопасности.

  4. External Entity Identity Propagation.
    Идея Identity Propagation с использованием API Gateway обеспечивает безопасную передачу Identity для некоей внешней сущности, External Entity (это может быть пользователь или система) нижележащим сервисам. API Gateway играет роль компонента, валидирующего внешнюю identity и передающего (propagating) надежным способом информацию об identity и связанных с ней атрибутов конечным сервисам для выполнения уже fine‑grained проверок. При этом обеспечивается целостность и аутентичность этой самой информации.

  5. Контроль применения вышеописанных практик для всех сервисов в ландшафте.
    Использование API Gateway позволяет нам применять упомянутые практики консистентно для большого набора сервисов.

Справедливости ради нужно сказать, что в посылах об «обязательности» или «большой важности» компонента, реализующего подход API Gateway, есть также и много маркетинга, в том числе со стороны компаний, зарабатывающих на продаже и внедрении подобных решений.

API security architecture

Весной этого года была опубликована книга Cloud Native Data Security with OAuth: A Scalable Zero Trust Architecture, в ней авторы говорят о понятии API security architecture, под которым понимают обеспечение безопасного доступа к API и к данным, которые через эти API предоставляются.

И описывая такую концепцию, авторы выделяют три функции и три соответствующих ключевых компонента для каждой функции:

  1. Identity & access management — Authorization server

  2. API management — API Gateway

  3. Entitlement management — Policy engine

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

К тому же подобная мысль хорошо ложится как раз на подходы, которые будут рассмотрены далее.

Про саму книгу

Книжка, кстати, довольно интересная, поэтому может быть полезно ознакомиться. Да, она аффилирована с компанией Curity, где авторы как раз работают, но зато те же Curity ее бесплатно раздают в электронном виде.

Работа с токенами доступа при использовании API Gateway

Можно выделить следующие основные подходы по работе с access token при использовании API Gateway:

  1. Непрозрачный (opaque) access token и интроспекция на каждом компоненте

  2. Непрозрачный (opaque) access token, интроспекция на API Gateway и распространение контекста в виде структурированных данных без подписи

  3. Access token всегда в виде JWT

  4. Два вида access token: reference (opaque) и self‑contained (JWT)

Далее рассмотрим эти подходы детальнее.

Непрозрачный (opaque) access token и интроспекция на каждом компоненте

Как RFC 6749 OAuth 2.0, так и спецификация OIDC не диктуют, какой формат должен иметь access token, вследствие чего его формат и структура могут определяться конкретными требованиями. Access token с точки зрения базовых стандартов — это некая абстрактная строка, представляющая делегированные client полномочия.

Opaque access token является токеном доступа, не предназначенным для понимания и непосредственного парсинга другими ролями, такими как client или resource server. Это непрозрачная строка, как правило представляющая собой указатель (reference, pointer) на данные самого токена, хранящиеся на стороне authorization server.

Непрозрачный токен не такой уж непрозрачный

Хоть я здесь и пишу о том, что данный токен должен восприниматься как абстрактная строка, из этого не следует, что он не может содержать в себе совсем никакой информации. Токены доступа могут иметь определенный префикс, позволяющий сразу определить окружение, в котором они получены, или таким образом обнаруживать токены вашей компании в различных утечках, например, на GitHub. Также в том или ином виде в нем могут быть закодированы иные сведения, например, используемые для роутинга: чтобы определить а куда именно нужно пойти за интроспекцией при наличии нескольких инсталляций authorization server.

Интроспекция, как нам говорит RFC 7662 OAuth 2.0 Token Introspection, представляет собой процесс, с помощью которого некий компонент (resource server, API Gateway) валидирует токен (может быть как access token, так и refresh token) запросом к introspection‑эндпоинту authorization server. Ответ на запрос, как правило, содержит метаданные токена: состояние активности, связанные claims (утверждения), scopes и прочие атрибуты, которые затем могут быть использованы для проверки контроля доступа.

Непрозрачный (opaque) access token и интроспекция на каждом компоненте
Непрозрачный (opaque) access token и интроспекция на каждом компоненте

В данном подходе API Gateway получает запрос с opaque‑токеном доступа, обращается к introspection‑эндпоинту authorization server, выполняет необходимые проверки и затем направляет запрос с тем же самым токеном далее конечному сервису. Конечный сервис, получив непрозрачный для него токен, также обращается к authorization server по аналогии и уже затем выполняет свои проверки. Здесь {data} — это ответ от introspection endpoint, он как раз содержит данные контекста для переданного access token.

Преимущества:

  • Никаких JWT и сопряженных с ними проблем

  • Явно проверяем в двух местах валидность access token, информация всегда актуальна

Недостатки и сложности:

  • Два вызова по сети — увеличивается общая latency запросов и нагрузка на authorization server

  • Необходимо предусмотреть меры для защиты от получения запроса сервисом в обход API Gateway.

Как видно из описания, слабое место подобного подхода заключается в необходимости делать неоднократные вызовы authorization server для каждого запроса. Поэтому существуют и пути оптимизации:

  • Использование кэширования в различных точках

  • На стороне authorization server хранение токенов доступа и сопутствующего контекста в памяти для более быстрого и дешевого извлечения

  • Оптимизация сетевого взаимодействия: connection pools, HTTP/2, сжатие полезной нагрузки (payload) для запросов/ответов.

При этом встречал примеры, в которых разработчики сумели у себя достаточно хорошо оптимизировать подобный процесс, оставляя общую latency запросов в допустимых для себя пределах.

Непрозрачный (opaque) access token, интроспекция на API Gateway и распространение контекста в виде структурированных данных без подписи

Непрозрачный (opaque) access token, интроспекция на API Gateway и распространение контекста в виде структурированных данных без подписи
Непрозрачный (opaque) access token, интроспекция на API Gateway и распространение контекста в виде структурированных данных без подписи

Здесь API Gateway получает запрос с opaque‑токеном доступа, обращается к introspection‑эндпоинту authorization server, выполняет необходимые проверки и затем передает ответ от introspection‑эндпоинта в виде структурированных сериализованных данных (например, JSON) вместе с запросом конечному сервису.

Проблема здесь в том, что в таком случае со стороны конечного сервиса мы должны на основании чего‑то доверять тому, кто присылает нам запрос, содержащий только данные access token, поскольку возможности проверить целостность и аутентичность этой информации у нас нет. И действительно, этот «некто» может запросто прислать нам запрос с любыми данными. И таких акторов может быть много. Ну а выполнять проверку авторизации только на API Gateway и пускать далее запрос с подразумеваемым доверием (implicit trust) советуют только очень смелые люди, потому что в таком случае API Gateway должен быть каким‑то образом «гвоздями прибит» к вашему сервису.

Преимущества:

  • Никаких JWT и сопряженных с ними проблем

  • Один вызов для интроспекции

Недостатки и сложности:

  • Необходимо обеспечить доверие конечных сервисов к полученной информации о токене (ведь на ее основе выполняется контроль доступа)

  • Необходимо предусмотреть меры для защиты от получения запроса сервисом в обход API Gateway.

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

Access token всегда в виде JWT

Access token всегда в виде JWT
Access token всегда в виде JWT

Подход, который наверняка знаком многим. В этом случае access token изначально представлен в виде JWT, а если быть точным, в виде одного из его физических представлений: JWS или JWE. В таком виде его выпускает authorization server, в таком виде он передается со стороны client. Здесь и далее будем подразумевать именно JWS‑представление JWT‑токена доступа как наиболее часто встречающееся.

Как видно из вышепредставленной схемы, после получения API Gateway access token никакого запроса к authorization server не выполняется. Это обусловлено наличием самой информации непосредственно в токене и возможностью выполнить локальную проверку его подписи при наличии на стороне API Gateway актуального публичного ключа из ключевой пары, приватный ключ которой был использован при подписи, если мы говорим про ассиметричную криптографию. Обычно распространение подобных публичных ключей в ландшафте выполняется с использованием JWKS URI.

Подход имеет большую популярность в силу кажущейся простоты, а также продвижения в комьюнити. При этом как основные преимущества обычно выделяют как раз простоту, удобство использования в сочетании с микросервисами, возможность горизонтального масштабирования (еще бы оно всем было нужно, правда) и повышенную безопасность, что бы это ни значило.

Основная особенность заключается в том, что многое из этого зависит от времени жизни такого access token. Магии не бывает, и чем меньше время жизни у JWT‑access token, тем в итоге больше все равно будет нагрузка на authorization server. Фактически без использования refresh‑токенов использование токена доступа в виде JWT смысла не имеет.

От выбора времени жизни токенов будет зависеть и используемый подход к их инвалидации. Известно, что по умолчанию возможность инвалидировать уже выпущенный JWT отсутствует ввиду особенностей, заложенных в его дизайн, поэтому в ряде случаев полагаются только на истечение его времени жизни. Также в том числе числе существует довольно распространенный подход утилизации «черных списков» отозванных токенов, опирающийся, как правило на идентификатор самого JWT, помещаемый в claim jti, и возможность его асинхронного получения компонентами, валидирующими токен, для ведения в течение некоторого времени перечня идентификаторов, JWT с которыми следует считать отозванными.

Презумпция валидности токена

Примечательно, что в таком случае JWT при проверке обладает «презумпцией валидности»: токен по умолчанию валиден, обратное нужно доказать (подтвердив наличие идентификатора в черном списке). Так наглядно можно увидеть, что если идентификатор для черного списка по какой‑то причине не будет вовремя доставлен, согласно данной презумпции токен будет считаться валидным.
Это может быть проэксплуатировано, если атакующий обнаружит возможность вызвать отказ в обслуживании (DoS) для одного из компонентов, участвующих в доставке идентификаторов для отозванных токенов. Поэтому для повышения вероятности обнаружения таких сбоев можно использовать периодическую передачу heartbeat от отправителя по тому же самому каналу связи. Чтобы вне зависимости от наличия или отсутствия сообщений можно было определить, функционирует ли цепочка доставки сообщений или нет.

Если бы при проверке токена было обращение к authorization server, имела бы место «презумпция невалидности» токена: токен по умолчанию невалиден, обратное нужно доказать (получив подтверждение от authorization server). Таким образом случае недоступности authorization server токен будет признан невалидным в отсутствие доказательств обратного, что может быть актуально при сбоях или недоступности компонентов authorization server.

Мысль, на мой взгляд, достаточно интересная, предлагаю ее также держать в голове.

При этом данный подход действительно может быть полезен и позволять определенное масштабирование и снижение latency в рассматриваемом сценарии, однако его реализация с действительным обеспечением безопасности и надежности не так проста и имеет ряд сложностей, решения для которых необходимо предусмотреть.

Преимущества:

  • Возможно снижение latency и нагрузки на authorization server

  • Возможно масштабирование сервисов с API в меньшей зависимости от authorization server

  • Сервисы и API Gateway работают с self‑contained JWT, локально проверяя его подпись

Недостатки и сложности:

  • Важно поддерживать короткое время жизни JWT, что вынуждает больше думать над обновлением токенов

  • Необходимо обеспечить надежный механизм отзыва токенов

  • В случае непреднамеренного доступа к сервису извне появляется возможность выполнить запрос напрямую, обойдя API Gateway

  • Презумпция валидности JWT

  • JWT может раскрывать некоторую информацию «наружу»
    С одной стороны здесь можно сказать, что мы же не помещаем в состав нашего токена доступа какую‑то конфиденциальную, секретную информацию. Это справедливо, но таким образом мы можем все равно раскрыть информацию об устройстве «внутренностей» нашей системы, что некоторый атакующий может использовать.

  • JWT — контракт между authorization server и client
    Если мы используем JWS, client может декодировать payload и начать опираться на claims в нем. И тогда JWT становится пусть неявным, но контрактом между authorization server и client. И если мы захотим изменить JWT в будущем, нам придется обеспечивать работоспособность не только для всех resources, но и для всех clients.

Два вида access token: reference (opaque) и self-contained (JWT)

Два вида access token: reference (opaque) и self-contained (JWT)
Два вида access token: reference (opaque) и self-contained (JWT)

С моей точки зрения, наиболее интересный подход, часто обделяемый вниманием. Authorization server здесь изначально выпускает для сlient непрозрачный opaque‑токен доступа. Со стороны сlient на API Gateway приходит запрос с ним, после чего API Gateway обращается в абстрактный security token service у authorization server, передавая данный токен, а также по‑хорошему обязательное подтверждение своей аутентификации (способы для этого выходят за рамки статьи), в том числе чтобы на основании него можно было выполнить проверку контроля доступа для предотвращения успешного выполнения вызовов прочими акторами, которые этого делать не должны.

Далее API Gateway получает ответ в виде нового токена доступа: подписанного authorization server JWT, выполняет необходимые проверки, и передает запрос уже конечному сервису, при этом заменяя изначальный opaque‑токен на self‑contained JWT, например, используя заголовки запроса. Конечному сервису остается, как и в предыдущем случае, проверить подпись токена с помощью соответствующего публичного ключа authorization server. Преимущество здесь в том, что сервисы доверяют по‑прежнему только authorization server, подписанные другими источниками токены проверку проходить не должны.

Self-contained token != JWT

В данной статье self‑contained токены рассматриваются на примере JWT, однако нужно понимать, что для этого могут быть использованы не только они. Так вы можете использовать собственный формат кодирования информации, что некоторые компании и делают.

Соответственно при таком подходе мы получаем с одной стороны возможность работы с JWT на стороне конечных сервисов, включая локальную проверку без запроса к authorization server, а с другой непрозрачные access token «снаружи» и только одно обращение к authorization server для обработки каждого запроса. Так автоматически закрываем возможность прямого обращения к какому‑либо сервису, даже если он каким‑то образом окажется доступным извне: без прохода через API Gateway мы не получим необходимый JWT, а с «внешним» opaque‑токеном сервисы попросту не будут знать, что делать.

При этом мы достигаем полной развязки двух представлений токена доступа, это фактически и есть два разных токена: они могут (и на самом деле, должны) иметь различное время жизни — что позволяет делать JWT максимально короткоживущими — и даже различную область действия, о чем поговорим далее. Также в ряде случаев это позволяет иметь различные внешние токены доступа, при этом сохраняя единообразие для внутренних.

Однако применение данного подхода также может сказываться на latency за счет проверок подписи токена, которая в ряде случаев может быть небыстрой.

Преимущества:

  • Разделение токенов на внешний и внутренний: сервисы не принимают внешний токен доступа, внутри «периметра» соблюдается единообразие

  • Для внутреннего JWT‑токена доступа возможно использовать отдельное короткое время жизни и дополнительно ограничивать область действия

  • Внешний токен может быть stateful

  • Client в общем случае не получает «на руки» JWT и не будет использовать его не по назначению

  • Сохраняем все преимущества от использования JWT

Недостатки и сложности:

  • Более сложная и трудоемкая реализация, не везде поддерживаемая «из коробки»

  • Остается негативное влияние на latency за счет дополнительного внешнего вызова и возможных медленных проверок подписи JWT.

Особенности реализации

Также для последнего подхода стоит отметить особенности реализации.

Ранее уже было упомянуто использование security token service, рассмотрим это понятие более подробно. Security token service (STS) называют в общем случае сервис, способный валидировать пришедшие к нему на вход токены и выпускать на их основании другие токены, что позволяет работать с нужными временными учетными данными (temporary credentials) в том числе в гетерогенных средах или между различными доменами безопасности. Примером таких временных учетных данных является как раз access token. Добавлю, что STS не обязательно должен быть реализован в виде отдельного сервиса и может являться частью сервиса, например, реализующего компонент authorization server.

Реализации могут различаться, но концептуально описанный подход с получением нового JWT‑токена доступа на основе другого существующего токена доступа встречается выполненным с помощью:

  • OAuth 2.0 Token Introspection;

  • OAuth 2.0 Token Exchange.

Казалось бы, причем здесь token introspection? Ведь ранее мы уже говорили про нее, и никаких новых токенов там не было. Однако существует интересный подход, получивший статус RFC в начале этого года, — RFC 9701 JSON Web Token (JWT) Response for OAuth Token Introspection.

И есть даже IAM‑решения, имплементировавшие его как раз для описываемой здесь задачи. При этом такой подход имеет одно важное отличие. Да, в результате подобного взаимодействий с introspection‑эндпоинтом мы можем получить на руки некий JWT. Однако нужно уточнить, что именно это за JWT: это лишь сформированная и подписанная та же самая информация из обычного ответа интроспекции, это не отдельный access token в прямом смысле этого слова. Поэтому для решения поставленной задачи вижу меньшую применимость данного подхода, поскольку он на самом деле не позволяет выполнить упомянутое ранее разделение токенов: нельзя таким использовать другое время жизни или же ограничить область действия.

Кстати, об этом говорит и сам вышеупомянутый RFC 9701:

The JWT SHOULD NOT include the sub and exp claims, as an additional measure to prevent misuse of the JWT as an access token (see Section 8.1).

Note: Although the JWT format is widely used as an access token format, the JWT returned in the introspection response is not an alternative representation of the introspected access token and is not intended to be used as an access token.

Другой способ заключается в использовании RFC 8693 OAuth 2.0 Token Exchange или его нестандартизированных аналогов, что тоже иногда встречается. В этом случае мы действительно получаем один токен на основании другого, и это будет именно отдельный токен. Стандартные возможности Token Exchange как раз позволяют дополнительно ограничивать новый получаемый токен. Также в этом случае мы можем отразить в выпускаемом JWT identity самого API Gateway, выполнившего обмен.


В завершение раздела хочу обозначить личное отношение к рассмотренным подходам. Импонирует подход с двумя видами токенов доступа, также может быть применим подход с интроспекцией на каждом компоненте, если его хорошо «приготовить». Использование access token, существующего всегда в виде JWT, может быть валидной опцией, однако в нем затруднительно применить меры, которые будут рассмотрены далее. Подход с передачей структурированных данных без подписи считаю скорее вырожденным, и его не следует использовать в реальных production‑ready системах.

Безопасность access token

Понимание вышеописанных подходов — это хорошо, но достаточно ли этого? В этом разделе подумаем еще и над безопасным использованием токенов доступа.

Часто перед нами встает задача защиты от утечки токенов доступа и их неправомерного использования, включая переиспользование (помним про replay attacks). Действительно, важно помнить, что access token — это временные учетные данные, причем достаточно ценные. Так, например, если злоумышленник способен завладеть токеном доступа, в котором делегированы полномочия пользователя, то уже становится неважно, сколько и каких факторов пользователь использовал при аутентификации, как был аутентифицирован сам client. При утечке access token появляется возможность выполнять действия от лица пользователя в рамках делегированных полномочий, что особенно актуально для Bearer‑токенов. Стоит отметить, что утечка возможна не только снаружи, на стороне client, но и внутри периметра, например, на стороне resource server.

Подходы и направления

В связи с этим работа с риском утечки access token и защиты от дальнейшего его неправомерного использования важна и актуальна. Вообще для снижения подобного риска можно выделить 3 направления работы:

  1. Защита от самой возможности утечки

    1. Безопасная работа с токеном доступа на клиентской части, меры по защите от попадания в логи в немаскированном виде, защита от перехвата трафика и пр.

  2. Защита от возможности использования токена доступа нелегитимным актором

    1. Sender‑constrained токены (mTLS, DPoP)

    2. Ограничение времени жизни токена (также можно отнести и к третьему направлению)

  3. Ограничение области использования токена

    1. Ограничение через scopes

    2. Ограничение через audiences (resources)

Комплексные меры не должны ограничиваться только одним из них, однако здесь особое внимание хочу уделить именно третьему направлению, которое освещено меньше других.

В чем здесь основная проблема? Если один и тот же токен доступа может быть использован для доступа к любым API (ресурсам), это порождает риск создания «супертокена» — токена с неограниченной областью применения, — который дает его текущему обладателю (bearer) в случае компрометации доступ ко множеству сервисов. Так появляется вопрос: а как из «абстрактного access token» для чего угодно сделать ограниченный токен, чтобы его невозможно было использовать в непредназначенных местах?

Область действия и полномочия

Здесь важно отметить, что говоря о «супертокене» я подразумеваю именно неограниченную область действия самого токена, но не отсутствие проверки контроля доступа для конечного субъекта. Так, речь идет о доступе в рамках допустимых полномочий субъекта, на который не наложено дополнительных ограничений.

С одной стороны, как было сказано ранее, для ограничения области применения токена доступа можно утилизировать стандартные его claims (или параметры ответа introspection‑эндпоинта): scope и aud. Однако понятие scope обычно подразумевает «какой доступ доступ должен быть ограничен», нежели чем «где данный доступ может быть применен» и включает значения, обозначающие более категорию доступа для делегирования, нежели чем указатель на расположение или identity конкретного ресурса. Да, scope можно использовать и для подобных целей, что порой также встречается, однако это не всегда может быть удобно в дальнейшей эксплуатации. Поэтому далее для ограничения через audiences будем подразумевать использование claim'а aud.

Resource Indicators for OAuth 2.0

Для последнего не так давно был предложен подход, который формализовался в виде RFC 8707 Resource Indicators for OAuth 2.0. Это стандартизированный подход к ограничению области применения токена доступа конечным перечнем ресурсов, включающий и способы для дальнейшего сужения перечня ресурсов, что как раз позволяет получить новый токен с более узкой областью применения.

Но использование одного только данного стандарта не решает проблему до конца. Поскольку client изначально получает только один access token, ему все равно придется включать все resources и scopes, которые нужны для его работы. То есть определенное ограничение уже появляется, и это хорошо, но оно все еще может быть недостаточно узким для конкретных требований.

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

Эту проблему можно начать решать с уровня сlient: например, предложить получать различные токены доступа для обращения к различным ресурсам. И такая практика может быть применима, если сlient использует у себя нечто вроде BFF. Тогда подобный BFF может иметь refresh token с более широкой областью применения и при обращении за получением уже access token запрашивать его с уменьшенной областью действий. С одной стороны, это хорошо: наиболее привилегированный токен не покидает зону владения сlient, которому он был выдан. С другой же стороны, в таком случае все запросы придется пропускать через некий промежуточный backend со стороны client, что не всегда желаемо и необходимо.

Это является действующей альтернативой, однако конкретных преимуществ над рассматриваемыми подходами с точки зрения информационной безопасности, у такого варианта не вижу. Как и во всех случаях, «снаружи» у client все равно будет существовать некий «general‑purpose session ID», поскольку все эти ограничения применяются уже по достижении запросов серверной части client. При этом мы добавляем дополнительный уровень абстракции.

Развитие оригинальной идеи

Сама идея ограничить изначальный access token перечнем не только scopes, но и ресурсов валидная, но можно ли оптимизировать подход к сужению области применения? Где здесь точки улучшения?

На самом деле, неспроста ранее подробное внимание было уделено схемам с использованием API Gateway. Раз у нас есть API Gateway, который уже (в части схем) имеет обращение к authorization server, почему бы нам не утилизировать это взаимодействие с той же целью? API Gateway в том числе обладает информацией, куда именно нужно далее направить запрос, так почему бы ему при этом не добавлять определенные ограничения в изначальную область применения токена?

Рассмотрим подход подробнее на примере схемы с двумя видами access token. Также схожим способом это может быть реализовано и для схемы с интроспекцией на каждом компоненте. Теоретически применимость есть и для варианта с access token всегда в виде JWT, однако, если при этом добавится отсутствовавший ранее вызов authorization server с API Gateway, подход потеряет часть преимуществ: тогда совсем не будет смысла выдавать изначальный токен доступа в виде JWT для client.

Для заданного access token есть некоторый изначальный набор resources и scopes, для доступа к которым он был получен. API Gateway, получая подобный токен, выполняет запрос к authorization server для получения уже self‑contained токена доступа и при этом передает только необходимый (‑е) для конкретного API resources и scopes. Authorization server далее возвращает API Gateway ограниченный JWT‑токен доступа для узкого применения, который уже передается конечному нижележащему сервису.

Для примера отобразим данную последовательность шагов на следующей sequence‑диаграмме:

Важная деталь в том, что authorization server должен убедиться, что запрашиваемый при получении токена доступа в виде JWT перечень resources и scopes входит в подмножество изначально запрошенных resources и scopes для access token, чтобы мы всегда выполняли именно сужение области действия, то есть downscoping. Это можно наглядно представить, используя круги Эйлера:

Здесь появляется справедливый вопрос: откуда API Gateway должен знать, какие именно resources и scopes нужны для конкретного запроса?

Чтобы ответить на него, полезно вспомнить одну из ключевых функций API Gateway, которую упоминали ранее: публикацию на нем API. Причем здесь не так важно, как именно реализован API Management: в отдельной системе или через ведение файлов с OpenAPI‑спецификациями в Git. При публикации для API в целом и/или для отдельных методов указываются требуемые scopes и resources, с помощью чего мы в том числе проверяем, что токен доступа, не предназначенный для нужных ресурсов, не пройдет API Gateway. Владельцы API — это и есть сами конечные сервисы, на которые запросы и попадут, они же обычно и ответственны за их публикацию, поэтому про себя каждый сервис знает, какие scopes и resources ему нужны. Также возможны и другие способы доставки правил для проверки контроля доступа на API gateway, если таковые управляются централизованно в иной системе.

Сама идея подобного использования scopes не нова. Однако хочу отметить, что scopes могут не всегда использоваться или использовать «плохо», и в большой действующей системе просто так ввести их будет очень сложно (хотя и полезно). Подход с resource indicators в то же время может быть проще во внедрении на масштабе и позволит выбрать требуемый уровень гранулярности, что уже даст существенные улучшения в сравнении с их отсутствием.

Одного API Gateway недостаточно

Здесь логично отметить, что какие бы красивые буквы мы ни записали в содержимое токена доступа, если на них не обратят внимания, эффекта это не возымеет. Поэтому хочу подчеркнуть важность проверки таких claims как aud и scope не только на стороне API Gateway, но и в каждом конечном сервисе. Более того, это должно входить в перечень обязательных проверок, который каждый сервис выполняет для токена. Обычно это достигается не только написанием рекомендаций, но предоставлением потребителям (сервисам) библиотек или готовых реализаций.

Варианты оптимизации

В данном случае каждый запрос, поступающий на API Gateway, все еще инициирует отдельный вызов к authorization server для получения self‑contained токена доступа, что может быть важно для наиболее нагруженных API, а также для API с высокими требованиями к latency. И можно подумать о вариантах кэширования на стороне API Gateway, чтобы достичь ряда улучшений.

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

Однако здесь в любом случае вижу существующую проблему: теперь у нас есть некое хранилище (кэш), где хранится сразу большое количество JWT‑токенов доступа. Соответственно при компрометации данного хранилища возможна уже массовая утечка токенов доступа. Для общего случая пока сложно сказать, насколько приоритетен этот риск. Также здесь сразу есть некоторые меры его снижения в виде короткого времени жизни токенов и их ограниченной области действия.

Заключение

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

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

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

Больше материалов про аутентификацию и Identity & Access Management можно найти в моем телеграм‑канале:

401 Unauthorized: аутентификация и не только

@unauthz

Литература

Кроме явно упомянутых в статье ссылками ресурсов, предлагаю обратить внимание на следующие материалы.

Комментарии (1)


  1. AntonLarinLive
    18.11.2025 20:02

    Не приведёт ли целевая схема с обменом токенов к перегрузке Authorization Server и проседанию латентности API Gateway? Ведь если на каждый ресурс нужен JWT со своими aud и scope, то это приведёт к постоянному запросу новых токенов (читай - потоку операций генерации электронной подписи, которая, обычно значительно медленней проверки), да ещё и валидации JWT как на API Gateway, так и дальше на сервисах? Да, есть кэширование, но оно эффективно для одного Resource, а когда клиент последовательно ходит к разным, то кэширование как бы и не очень поможет.