JWT: от подписи до отказа в доступе
JSON Web Token (JWT) - это формат токена, который позволяет серверу проверить подлинность запроса без обращения к базе данных. Он состоит из трех частей: заголовка, содержащего метаданные о токене, полезной нагрузки с утверждениями о пользователе, и подписи, гарантирующей целостность. На первый взгляд это выглядит как идеальное решение для аутентификации, но за кажущейся простотой скрываются подводные камни.
Как JWT работает на практике: от байтов до проверки
Типичный JWT выглядит как строка из трех блоков, разделенных точками. Каждый блок представляет собой base64-кодированный JSON:
- Заголовок определяет алгоритм подписи и тип токена:
{
"alg": "HS256",
"typ": "JWT"
}
- Полезная нагрузка содержит утверждения (claims):
{
"sub": "user123",
"iat": 1672531200,
"exp": 1672617600
}
- Подпись создается путем подписания первых двух блоков секретным ключом.
Главная особенность JWT заключается в том, что серверу не нужно хранить состояние сессии - достаточно проверить подпись. Это обеспечивает высокую производительность, но создает фундаментальную проблему: невозможность мгновенного отзыва токена.
Тупик: почему простое решение не работает
Когда разработчики впервые сталкиваются с проблемой отзыва JWT, первое, что приходит в голову - сделать срок жизни токена очень коротким. Казалось бы, если токен будет действовать всего несколько минут, то даже если его украдут, злоумышленник не успеет им воспользоваться.
Однако на практике это решение порождает новые проблемы:
-
Лавина запросов на обновление Клиентское приложение вынуждено постоянно запрашивать новые токены. В мобильных приложениях, где пользователь может часто выходить из спящего режима, это приводит к значительному увеличению нагрузки на сервер.
-
Проблема обновляющего токена Чтобы избежать постоянного ввода учетных данных, вводится концепция refresh token. Но этот токен тоже нужно где-то хранить, и он тоже может быть украден. Получается, что мы просто переносим проблему на другой уровень.
-
Состояние гонки Даже если refresh token отозван, клиент может успеть получить новый access token до того, как сервер обработает запрос на отзыв. Это создает временное окно уязвимости.
-
Усложнение клиентской логики Клиентское приложение должно теперь обрабатывать множество сценариев: истечение токена, отзыв refresh token, сетевые ошибки при обновлении. Это значительно усложняет код и увеличивает вероятность ошибок.
Самое главное - короткий срок жизни токена не решает фундаментальную проблему: JWT по-прежнему нельзя отозвать мгновенно. Мы лишь уменьшаем окно уязвимости, но не устраняем саму проблему.
Три подхода к решению проблемы отзыва и их ограничения
Понимание того, что простое сокращение срока жизни токена не работает, приводит к поиску более сложных решений. Рассмотрим три основных подхода:
1. Чёрный список отозванных токенов
Сервер хранит список отозванных токенов и проверяет каждый входящий токен против этого списка.
Преимущества:
- Мгновенный отзыв токенов
- Простая реализация
Недостатки:
- Утрата безбазовости - сервер снова должен обращаться к хранилищу
- Проблема масштабирования при большом количестве отозванных токенов
- Необходимость синхронизации между несколькими серверами
2. Версия сессии
В полезную нагрузку добавляется версия сессии. При отзыве сессии сервер увеличивает версию в базе данных.
Преимущества:
- Не требует хранения всех отозванных токенов
- Сохраняет безбазовость для проверки подписи
Недостатки:
- Требует синхронизации версии между серверами
- При частых изменениях версии создает нагрузку на базу данных
- Усложняет логику проверки токенов
3. Короткий срок жизни + обновляющий токен
Комбинация короткоживущего access token и долгоживущего refresh token.
Преимущества:
- Уменьшает окно уязвимости
- Позволяет отзывать сессии через refresh token
Недостатки:
- Увеличивает нагрузку на сервер
- Refresh token тоже может быть украден
- Создает состояние гонки при отзыве
- Усложняет клиентскую логику
Каждое из этих решений - это компромисс между безопасностью, производительностью и сложностью реализации.
Четыре сценария, где JWT не подходит
Несмотря на популярность JWT, существуют ситуации, где его использование неоправданно или даже опасно:
1. Длинные сессии с мгновенным отзывом
В системах, где требуется возможность мгновенного выхода со всех устройств (например, банковские приложения), JWT создает неприемлемые риски безопасности. Даже с коротким сроком жизни токена существует окно уязвимости.
2. Динамические права доступа
В системах с часто меняющимися правами доступа JWT становится источником проблем. Сервер не может мгновенно отозвать токен при изменении прав пользователя.
3. Хранение конфиденциальных данных
JWT не предназначен для хранения секретной информации. Полезная нагрузка подписана, но не зашифрована, что делает ее уязвимой для перехвата.
4. Высоконагруженные системы с жесткими требованиями к безопасности
В системах, где безопасность критически важна, а нагрузка высока, JWT может создавать больше проблем, чем решать. Альтернативные решения с серверным хранением сессий могут оказаться более надежными.
Типовые ошибки реализации JWT
Даже при правильном выборе JWT, ошибки в реализации могут свести на нет все преимущества:
-
Доверие алгоритму из заголовка Заголовок токена может быть подменен. Сервер должен игнорировать алгоритм из заголовка и всегда использовать заранее определенный алгоритм.
-
Отсутствие проверки срока жизни Без проверки поля
expтокен становится вечным, что открывает возможности для replay-атак. -
Использование слабых алгоритмов подписи Алгоритмы вроде HS256 требуют общего секрета, который может быть скомпрометирован. В критичных системах лучше использовать асимметричные алгоритмы.
-
Хранение конфиденциальных данных в payload Все, что помещается в полезную нагрузку, может быть прочитано любым, кто получит токен.
-
Неправильная проверка подписи Использование неверного ключа для проверки подписи делает всю защиту бесполезной.
Почему JWT выбирают, даже когда он не подходит
Несмотря на все ограничения, JWT остается популярным решением по нескольким причинам:
-
Мода и следование трендам Многие разработчики выбирают JWT просто потому, что “так делают все”, не анализируя реальные потребности системы.
-
Непонимание ограничений Часто разработчики не до конца понимают фундаментальные ограничения JWT, особенно в части отзыва токенов.
-
Упрощение архитектуры JWT действительно упрощает архитектуру за счет устранения необходимости хранения сессий на сервере, но это упрощение может дорого обойтись в плане безопасности.
-
Маркетинговые обещания Многие статьи и руководства преувеличивают преимущества JWT, не акцентируя внимание на его ограничениях.
Позиция редакции
JWT - это мощный инструмент, но не универсальное решение. Его использование должно быть обосновано реальными потребностями системы, а не модой или маркетинговыми обещаниями.
Основные тезисы нашей позиции:
-
JWT подходит для систем, где:
- Нет необходимости в мгновенном отзыве сессий
- Права доступа меняются редко
- Производительность важнее гибкости управления сессиями
-
JWT не подходит для систем, где:
- Требуется мгновенный отзыв сессий
- Права доступа могут меняться часто
- Безопасность критически важна
-
При использовании JWT необходимо:
- Тщательно выбирать алгоритм подписи
- Никогда не доверять заголовку токена
- Всегда проверять срок жизни
- Никогда не хранить конфиденциальные данные в payload
-
Альтернативы JWT:
- Для простых систем - традиционные сессии с серверным хранилищем
- Для распределенных систем - комбинация JWT с дополнительными механизмами отзыва
Позиция неверна, если в вашей системе критически важна возможность мгновенного отзыва сессий или часто меняются права доступа пользователей. В таких случаях лучше рассмотреть альтернативные решения.