Перейти к содержимому
Toova
Все инструменты

Что такое JWT и как безопасно его декодировать

Toova

JSON Web Token встречается повсюду в современной веб-разработке — заголовки аутентификации, OAuth-потоки, API-ключи, управление сессиями. Если вы когда-либо работали с API, требующим токена Bearer, вы уже использовали JWT. Однако большинство разработчиков воспринимают их как непрозрачные строки и редко заглядывают внутрь. Понять структуру JWT, значение каждой части и как декодировать токен без нарушений безопасности можно за пятнадцать минут. Это руководство охватывает всё необходимое.

Анатомия JWT

Каждый JWT представляет собой строку из трёх сегментов, закодированных в Base64URL и разделённых точками:

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyXzEyMyIsImlzcyI6Imh0dHBzOi8vYXV0aC5leGFtcGxlLmNvbSIsImF1ZCI6Imh0dHBzOi8vYXBpLmV4YW1wbGUuY29tIiwiZXhwIjoxNzQ3MDAwMDAwLCJpYXQiOjE3NDY5OTY0MDAsInNjb3BlIjoicmVhZDp1c2VycyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Формат: заголовок.полезная_нагрузка.подпись. Каждый сегмент кодирует разную информацию.

Заголовок

Первый сегмент — заголовок. В декодированном виде приведённый выше пример выглядит так:

{
  "alg": "RS256",
  "typ": "JWT"
}

alg определяет алгоритм, использованный для подписи токена — RS256 означает RSA с SHA-256. typ идентифицирует тип токена. Эти два поля минимально необходимы; некоторые токены также включают kid (идентификатор ключа), чтобы сообщить верификатору, какой публичный ключ использовать при наличии нескольких ключей в ротации.

Полезная нагрузка

Второй сегмент — полезная нагрузка, содержащая фактические данные. В декодированном виде:

{
  "sub": "user_123",
  "iss": "https://auth.example.com",
  "aud": "https://api.example.com",
  "exp": 1747000000,
  "iat": 1746996400,
  "scope": "read:users"
}

Полезная нагрузка содержит утверждения (claims): сведения о сущности, которую представляет токен. Некоторые утверждения стандартизированы (называются зарегистрированными); другие зависят от приложения (называются частными). Полезная нагрузка закодирована в Base64URL, а не зашифрована. Любой, кто получит строку токена, может прочитать каждое утверждение внутри.

Подпись

Третий сегмент — подпись. Она создаётся путём конкатенации закодированного заголовка и полезной нагрузки через точку, после чего результат подписывается секретным или приватным ключом, указанным в alg:

signature = sign(
  base64url(header) + "." + base64url(payload),
  secretOrPrivateKey
)

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

Как декодировать JWT

Декодирование отличается от верификации. Декодирование просто читает данные внутри токена. Верификация подтверждает, что токен подлинный и не истёк. В производственном коде всегда следует верифицировать; декодирование без верификации полезно только для отладки.

Шаг 1: разбить токен

Разбейте строку JWT по символу .. Вы получите три сегмента. Если их меньше трёх — токен сформирован неправильно.

Шаг 2: декодировать заголовок и полезную нагрузку

Декодируйте каждый из первых двух сегментов из Base64URL. Base64URL похож на стандартный Base64, но использует - вместо + и _ вместо /, без символов заполнения. После декодирования каждый сегмент можно разобрать как JSON.

Шаг 3: разобрать и проверить

Прочитайте утверждения. Сравните exp (время истечения) с текущей меткой Unix. Проверьте iss и aud, чтобы убедиться, что токен предназначен для вашего приложения. Не доверяйте ни одному утверждению, пока не верифицировали подпись.

Для быстрого просмотра в процессе разработки используйте декодер JWT. Он работает полностью в вашем браузере — токен никуда не передаётся. Вы также можете вручную декодировать сегменты Base64 с помощью кодировщика/декодировщика Base64, чтобы увидеть необработанные байты. Для проверки подписей в тестовых скриптах генератор HMAC позволяет воспроизвести подписи HS256 без написания кода.

Шаг 4: верифицировать подпись в коде

В production всегда верифицируйте подпись с помощью библиотеки, предназначенной для вашего языка. Не реализуйте верификацию подписи вручную. Популярные варианты: jose или jsonwebtoken для Node.js, PyJWT для Python, golang-jwt/jwt для Go и nimbus-jose-jwt для Java.

Всегда явно передавайте алгоритм. Никогда не позволяйте библиотеке выводить алгоритм из заголовка токена.

Стандартные утверждения (зарегистрированные claims)

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

iss — Issuer (Издатель)

Сущность, создавшая и подписавшая токен. Обычно это URL, идентифицирующий сервер авторизации (например, https://auth.example.com). Верификаторы должны проверять, что iss соответствует ожидаемому значению, и отклонять токены от неизвестных издателей.

sub — Subject (Субъект)

Принципал, которого представляет токен — обычно ID пользователя, имя сервисного аккаунта или идентификатор устройства. Значение должно быть уникальным в контексте издателя. Ваше приложение использует sub, чтобы определить, какому пользователю принадлежит запрос.

aud — Audience (Аудитория)

Предполагаемый получатель(и) токена. Если идентификатор вашего API — https://api.example.com, токены, выданные для других сервисов, следует отклонять. Несоблюдение проверки aud позволяет злоумышленникам повторно использовать токен, полученный от одного сервиса, на другом.

exp — Expiration Time (Время истечения)

Временная метка Unix, после которой токен не должен приниматься. Всегда проверяйте exp. Токен без срока истечения фактически действителен вечно — в случае кражи злоумышленник получает бессрочный доступ.

iat — Issued At (Время выдачи)

Временная метка Unix, когда был создан токен. Полезна для обнаружения токенов, которые технически не истекли, но подозрительно старые. iat можно использовать для ограничения максимального возраста токена независимо от exp.

nbf — Not Before (Не раньше чем)

Временная метка Unix, до которой токен не должен приниматься. Встречается реже, чем exp, но полезна при заблаговременной выдаче токенов — например, для запланированной задачи, которая не должна стартовать раньше определённого момента.

jti — JWT ID (Идентификатор JWT)

Уникальный идентификатор токена. Позволяет издателям предотвращать атаки повторного воспроизведения, сохраняя использованные значения jti и отклоняя токены с дублирующимися ID. Необходим для одноразовых токенов, например ссылок для сброса пароля.

Уязвимости безопасности

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

Атака «alg: none»

Некоторые библиотеки JWT ранних версий стандарта принимали токен, в заголовке которого был указан "alg": "none". Злоумышленник мог взять любой действительный токен, заменить алгоритм на none, удалить подпись — и уязвимая библиотека принимала его как полностью действительный, без какой-либо подписи.

Защита: всегда явно указывайте ожидаемый алгоритм при вызове функции верификации. Считайте любой токен с alg: none недействительным. Большинство современных библиотек устранили эту проблему, но перед выходом в production стоит убедиться в поведении вашей библиотеки по умолчанию.

Путаница алгоритмов (понижение RS256 до HS256)

Некоторые библиотеки с поддержкой обоих алгоритмов использовали поле alg в заголовке токена для выбора метода верификации. Злоумышленник мог изменить алгоритм на HS256 и подписать токен публичным ключом сервера в качестве HMAC-секрета. Сервер, видя HS256, проверял токен по публичному ключу и принимал поддельный токен.

Защита та же: фиксируйте алгоритм в своём коде верификации. Никогда не позволяйте заголовку токена влиять на выбор алгоритма для верификации.

Принятие просроченных токенов

Отсутствие проверки exp — распространённая ошибка, которую иногда вносят разработчики, добавляя период допуска, постепенно разрастающийся до бесконечности, или когда код проверки обходится в какой-то ветке. Относитесь к просроченному токену так же, как к отсутствующему: отклоняйте с ответом 401 и требуйте от клиента повторной аутентификации или использования токена обновления.

Чувствительные данные в полезной нагрузке

Полезная нагрузка закодирована в Base64URL, а не зашифрована. Любой, кто перехватит токен, может её декодировать. Не помещайте пароли, номера банковских карт, номера социального страхования и другие чувствительные данные в полезную нагрузку. Если вам нужно безопасно передавать конфиденциальные утверждения, используйте токен JWE (JSON Web Encryption), который шифрует полезную нагрузку. Токены JWT, подписанные через JWS (распространённый случай), гарантируют только подлинность, но не конфиденциальность.

Отсутствие проверки аудитории

Пропуск проверки aud означает, что токен, выданный для Сервиса A, может быть воспроизведён на Сервисе B — при условии, что оба сервиса используют один ключ подписи или доверяют одному издателю. В многосервисных архитектурах всегда проверяйте, что аудитория токена соответствует идентификатору вашего сервиса.

Лучшие практики на 2026 год

Выбирайте RS256 для большинства приложений

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

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

Делайте токены доступа краткосрочными

Встроенного механизма отзыва JWT не существует — после выдачи токен действителен до истечения срока (если только вы не реализуете блок-лист, который вновь вводит серверное состояние). Короткие сроки жизни (15 минут — 1 час) ограничивают ущерб в случае кражи токена. Используйте краткосрочные токены доступа совместно с долгосрочными токенами обновления, хранящимися в защищённых cookie HttpOnly, а не в localStorage.

Используйте ротацию токенов обновления

Когда клиент использует токен обновления для получения нового токена доступа, выдавайте новый токен обновления и аннулируйте старый. Таким образом кража токена обновления обнаруживается при следующей попытке легитимного клиента использовать оригинальный: сервер видит повторно используемый токен обновления и может отозвать всю сессию. Этот паттерн описан в RFC 6819 и широко поддерживается современными серверами авторизации.

Публикуйте JWKS-эндпоинт

Эндпоинт JSON Web Key Set (JWKS) (/.well-known/jwks.json по соглашению) публикует ваши публичные ключи подписи в стандартном формате. Другие сервисы могут получить JWKS и верифицировать токены без предоставления ключей вне диапазона. Это также упрощает ротацию ключей: добавьте новый ключ в JWKS, начните подписывать им, затем удалите старый после истечения всех существующих токенов.

Регулярно ротируйте ключи

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

Проверяйте каждое утверждение

Проверяйте iss, aud, exp и nbf при каждом запросе. Не пропускайте ни одно из них из-за кажущейся избыточности в вашей конфигурации — именно те условия, при которых они кажутся лишними сегодня, меняются во время инцидента.

JWT против сессионных cookie

JWT и сессии на стороне сервера решают одну и ту же задачу — сохранение состояния аутентификации между запросами HTTP без сохранения состояния — но с разными компромиссами.

JWT самодостаточны. Серверу не нужно обращаться к базе данных для проверки запроса. Это делает их привлекательными для распределённых систем и API, используемых мобильными или сторонними клиентами. Недостаток: отзыв требует блок-листа (что возвращает серверное состояние) или допущения оставшегося времени жизни скомпрометированного токена.

Сессионные cookie проверяются сервером. Сервер хранит состояние сессии и может мгновенно отозвать доступ, удалив сессию. Cookie с флагами HttpOnly и Secure защищены от доступа JavaScript и перехвата по сети. Недостаток: каждый запрос требует обращения к базе данных, что при масштабировании может стать узким местом.

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

Декодируйте ваш токен

Самый быстрый способ проверить JWT — вставить его в декодер JWT. Он разбивает токен, декодирует заголовок и полезную нагрузку и отображает утверждения в читаемом формате — без передачи токена куда-либо. Если вам нужно вручную кодировать или декодировать строки Base64URL, кодировщик/декодировщик Base64 поддерживает как стандартный, так и URL-безопасный варианты. Для проверки HMAC-подписей в тестовых скриптах генератор HMAC позволяет воспроизвести подпись HS256 и сравнить её с тем, что содержит ваш токен.

Полную спецификацию смотрите в RFC 7519 (JWT) и интерактивных примерах на jwt.io.