
Введение
JSON Web Token (JWT) приобрёл популярность как удобный способ аутентификации и передачи данных между клиентом и сервером. Разработчики ценят его за простоту, независимость от состояния (stateless) и возможность легко передавать информацию о пользователе между сервисами. Однако большинство материалов и гайдов рассказывают только о плюсах, упуская важные недостатки и потенциальные угрозы безопасности.
В этой статье мы разберём основные проблемы использования JWT для хранения пользовательских сессий и обсудим более надёжные альтернативы.
Что такое JWT?
JWT (JSON Web Token) – это стандартный формат токенов, который используется для передачи информации между двумя сторонами в безопасной и компактной форме. JWT состоит из трёх частей, закодированных в Base64 и разделённых точками:

-
Header (Заголовок) – содержит информацию о типе токена и алгоритме подписи (например, HMAC или RSA). Пример заголовка:
{ "alg": "HS256", "typ": "JWT" }
-
Payload (Полезная нагрузка) – включает в себя утверждения (claims), содержащие информацию о пользователе и его правах. В payload можно добавить стандартные, частные и публичные claims. Например:
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 }
Здесь sub – идентификатор пользователя, name – его имя, а iat – время выпуска токена.
-
Signature (Подпись) – используется для защиты целостности токена. Она создаётся на основе заголовка и полезной нагрузки с использованием секретного ключа или пары ключей (приватного и публичного). Пример подписи на HMAC:
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
Если злоумышленник изменит payload, то подпись перестанет совпадать, и сервер отклонит токен.
JWT обычно используется в процессах аутентификации и авторизации. После успешного входа пользователя сервер создаёт JWT и отправляет его клиенту. В последующих запросах клиент передаёт токен, подтверждая свою личность, что позволяет избежать дополнительных обращений к базе данных.
Несмотря на удобство, важно понимать ограничения и потенциальные риски использования JWT в качестве механизма управления сессиями.
Почему использование JWT для сессий – бессмысленная затея
JWT (JSON Web Token) изначально разрабатывался для передачи авторизационных данных между сервисами в распределённых системах, а не для управления пользовательскими сессиями. Но почему-то многие разработчики решили, что раз он «stateless» и удобен в API, то отлично подойдёт и для аутентификации пользователей. На практике это приводит к куче проблем, делает систему сложнее и менее безопасной. Давайте разберёмся, почему JWT – плохая идея для управления сессиями.
1. Невозможность немедленного разлогинивания
Главная проблема с JWT в том, что он самодостаточен. Это значит, что сервер, выдав токен, больше никак его не контролирует. Если пользователь выходит из системы, токен остаётся валидным до истечения срока жизни. Если токен украли, его невозможно отозвать – злоумышленник сможет использовать его до конца срока действия.
Некоторые предлагают хранить список отозванных токенов (revocation list), но это полностью убивает смысл «stateless» механизма, ведь серверу снова нужно хранить состояние.
2. Устаревшие данные и проблемы с правами доступа
Данные в JWT неизменяемы. Если у пользователя изменились права доступа, он сменил пароль или его аккаунт заблокировали – он продолжает использовать старый токен, пока тот не истечёт. Это создаёт несколько серьёзных проблем:
-
Пользователь может получить доступ к ресурсам, к которым уже не должен иметь доступа.
-
У разных пользователей в системе могут быть разные уровни доступа в зависимости от того, насколько свежий у них токен.
-
Система становится непредсказуемой с точки зрения безопасности.
В традиционных сессиях всё просто – изменения применяются сразу, потому что информация о сессии хранится на сервере.
3. Длина токенов и избыточность
JWT, в отличие от обычных session ID, содержит не только идентификатор пользователя, но и данные (payload). Это делает его значительно длиннее и создаёт проблемы:
-
Длинные токены увеличивают нагрузку на сеть, так как пересылаются в каждом запросе.
-
Они могут не влезать в заголовки HTTP-запросов.
-
Их неудобно хранить в куках или localStorage.
Вместо простого session ID размером в 32 байта, мы получаем перегруженный токен, который тащит с собой лишние данные, большая часть которых вообще не нужна в каждом запросе.
4. Уязвимость к атакам
JWT содержит полезную нагрузку (payload), которая хоть и подписана, но не зашифрована. Любой, кто получит токен, сможет его прочитать. Это создаёт угрозу утечек:
-
Злоумышленник, перехватив JWT, может узнать ID пользователя и другие данные.
-
Если алгоритм подписи слабый или ключ украден, токен можно подделать.
-
Если JWT хранится в localStorage, он подвержен атакам XSS.
Традиционные сессии с защищёнными куками (httpOnly, secure, SameSite=Strict) гораздо безопаснее.
Но ведь JWT – это удобно и современно?
Разработчики, использующие JWT для сессий, часто приводят три ключевых аргумента:
он stateless, он масштабируем, он безопасен.
Давайте разберёмся, так ли это на практике.
1. «JWT удобен, потому что stateless!»
Звучит логично: сервер не должен хранить информацию о пользователе между запросами, а значит, не нужно думать о состоянии.
Но как только появляется необходимость отозвать токен (например, пользователь вышел, изменил пароль или его аккаунт заблокирован), оказывается, что без хранения состояния никуда.
Как решают эту проблему?
-
Списки отозванных токенов (revocation lists).
Сервер вынужден хранить данные обо всех отозванных токенах и проверять их при каждом запросе. Вроде бы JWT задумывался как stateless-решение, но теперь у нас снова появляется состояние. -
Короткоживущие access-токены + refresh-токены.
Раз JWT нельзя отозвать, его срок жизни сокращают до нескольких минут, а refresh-токены позволяют получать новые. Отличная идея, если не задумываться о последствиях: теперь у нас два типа токенов, сложная логика их обновления, и если злоумышленник получит refresh-токен, он без труда выпустит себе новый access-токен. -
Централизованное хранилище токенов.
Некоторые предлагают хранить JWT в базе и проверять его при каждом запросе. Но если всё равно приходится проверять данные на сервере, то почему бы сразу не использовать обычный session ID?
В итоге любая попытка сделать JWT управляемым приводит к необходимости хранения состояния.
А если в итоге приходится всё равно отслеживать сессии, зачем использовать механизм, который изначально создавался без этой возможности?
Выходит, что «stateless» в случае JWT — это либо миф, либо иллюзия, которая рассеивается при первом же реальном кейсе.
2. «JWT масштабируем и не требует базы данных!»
В теории JWT избавляет сервер от необходимости хранить информацию о сессиях. Но на практике современные базы данных и кэширующие системы уже давно решают эту проблему быстрее и эффективнее.
Session ID + Redis:
-
Работает быстрее и надёжнее, чем проверка подписи JWT в каждом запросе.
-
Позволяет моментально аннулировать сессии без сложных решений с чёрными списками.
-
Поддерживает горизонтальное масштабирование и отказоустойчивость.
JWT не даёт никакого уникального преимущества в плане масштабирования.
Все крупные системы, работающие с высокой нагрузкой, используют либо sticky-сессии, либо Redis с кластеризацией — и прекрасно справляются с этим без JWT.
3. «JWT защищён, ведь он подписан!»
Да, JWT подписан. Но не зашифрован.
А значит:
-
Любой, кто перехватит токен, сможет его прочитать. В отличие от session ID, который является просто случайной строкой, JWT содержит полезную нагрузку (payload), а в ней может быть ID пользователя, email, роли, а иногда даже права доступа.
-
Если злоумышленник получит токен (например, через XSS-атаку или утечку логов), он получит всю эту информацию.
-
Если кто-то узнает секретный ключ (signing key), он сможет подделывать JWT и выдавать себе любые права.
Подпись подтверждает, что данные не были изменены, но не защищает их от прочтения.
А если информация в токене действительно чувствительная, её надо шифровать, а не просто подписывать.
JWT в микросервисной архитектуре
Несмотря на проблемы JWT в контексте пользовательских сессий, в микросервисной архитектуре он действительно может быть полезен. Здесь ключевой момент в том, что JWT используется не как механизм сессий, а как средство авторизации между сервисами.
Какие у этого есть плюсы?
-
API Gateway как централизованная точка входа.
Когда у вас множество микросервисов, JWT позволяет один раз пройти аутентификацию, а затем сервисы могут проверять подпись токена локально, без постоянных обращений к базе данных или централизованному серверу авторизации. Это снижает нагрузку на систему и упрощает взаимодействие между сервисами. -
Дополнительные механизмы защиты.
Да, JWT сам по себе не защищает от компрометации, но можно внедрять fingerprint-механизмы — привязку токена к устройству, IP-адресу или другому идентификатору. Это не решает всех проблем, но минимизирует риски утечек и использования токена злоумышленниками. -
Минимизация запросов в базу.
В микросервисах важно минимизировать межсервисные запросы. Если каждый сервис будет обращаться к базе за валидацией сессии, это создаст дополнительную нагрузку. С JWT достаточно проверить подпись локально, без запроса в централизованное хранилище.
Однако, даже в микросервисной архитектуре стоит учитывать недостатки JWT:
-
Если токен украден, его нельзя отозвать без дополнительных механизмов (например, списка отозванных токенов в базе или Redis).
-
Refresh-токены всё равно нужно где-то хранить и проверять, а значит, полностью избавиться от состояния не получится.
-
Для чувствительных данных JWT не подходит, потому что его payload читается всеми, у кого есть токен.
Вывод
Каждой задаче — свой инструмент. Если у вас монолитное веб-приложение, проще и надёжнее использовать cookie-сессии с Redis.
Если у вас распределённая микросервисная архитектура, JWT может быть удобным инструментом для передачи авторизационных данных между сервисами, но не стоит забывать о проблемах JWT.
Используйте его там, где он действительно решает проблему, а не там, где его используют «потому что так модно».
Автор: Ange1oc