Skip to content
Назад в блог

JWT не шифрует данные: как читать и проверять токен

6 мин. чтения
JWT не шифрует данные: как читать и проверять токен

Строка вида eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMifQ.p0W4T... выглядит так, будто её содержимое скрыто шифрованием. Для самого распространённого варианта JWT это неверно: первые два сегмента можно преобразовать из Base64URL обратно в JSON без секрета и закрытого ключа. Защита такого токена заключается не в сокрытии claims, а в возможности обнаружить их подмену.

Из этого, однако, не следует, что любой JWT состоит из трёх открыто читаемых частей. JWT задаёт способ представить claims, а криптографическую форму ему дают соседние стандарты JWS и JWE. Различие между ними важно не только для терминологии, поскольку от него зависит, какие свойства токен действительно предоставляет и что именно должна проверить принимающая сторона.

От черновика 2010 года к семейству JOSE

Первый черновик JSON Web Token появился в IETF Datatracker в декабре 2010 года. После нескольких лет обсуждений работа перешла в OAuth Working Group и в мае 2015-го стала RFC 7519. Одновременно были опубликованы связанные спецификации семейства JOSE: JWS для подписи и message authentication code, JWE для шифрования, JWK для описания ключей и JWA для идентификаторов алгоритмов.

Задача JWT была достаточно узкой: создать компактное и пригодное для URL представление утверждений, которыми обмениваются две стороны. Стандарт не определял систему входа, серверную сессию или права пользователя и не обещал, что любой набор claims будет безопасен сам по себе. Эти смыслы задаёт протокол, использующий JWT, и правила конкретного приложения.

Именно поэтому возраст RFC не равен возрасту идеи. Черновики JWT уже применялись в ранней работе над OpenID Connect, пока комплект спецификаций JOSE проходил стандартизацию. Публикация 2015 года закрепила общий формат и словарь, но не создала все варианты использования токенов с нуля.

JWT, JWS и JWE выполняют разные задачи

RFC 7519 определяет JWT Claims Set как JSON-объект. Этот объект может стать payload структуры JWS или plaintext структуры JWE. В первом случае JWS защищает целостность и происхождение данных цифровой подписью либо MAC, а во втором JWE обеспечивает конфиденциальность и целостность зашифрованного содержимого. При необходимости подписанный JWT можно дополнительно зашифровать и получить nested JWT.

Чаще всего в API и отладчиках встречается JWS Compact Serialization. Такая строка состоит из трёх Base64URL-сегментов: защищённого header, payload и значения подписи или MAC. Когда payload содержит JWT Claims Set, первые два сегмента действительно можно прочитать без ключа. Base64URL лишь переводит байты в алфавит, подходящий для URL, и не пытается скрыть информацию.

Компактный JWS с JWT Claims Set и проверкой HMAC-SHA256

Иллюстрация показывает конкретный вариант HS256. Защищённый header и claims кодируются отдельно, после чего HMAC-SHA256 получает в качестве входа два первых сегмента вместе с разделяющей точкой. Полученное значение образует третий сегмент:

JWS = base64url(header) + "." + base64url(payload) + "." + base64url(MAC)

MAC = HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)

Компактный JWE устроен иначе и содержит пять сегментов: header, encrypted key, initialization vector, ciphertext и authentication tag. Claims без ключа из него не извлечь. Поэтому фраза «payload любого JWT открыт» слишком широка; она верна для обычного незашифрованного JWS, но не для JWE.

Стандарт также описывает unsecured JWT с alg: none и пустым третьим сегментом. Это не запасной вариант на случай ошибки проверки, а режим для заранее согласованного контекста без криптографической защиты. RFC 8725 рекомендует библиотекам не принимать такие токены, если приложение явно их не запросило.

Подпись подтверждает байты, но не истинность claims

При HS256 издатель и проверяющая сторона знают один HMAC-секрет. Такая схема проста внутри одной доверенной системы, однако каждый получатель секрета технически способен создавать собственные токены. RS256, PS256 и ES256 разделяют роли: издатель подписывает закрытым ключом, а сервисы проверяют результат открытым и не получают возможности выпускать новые подписи.

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

Безопасный валидатор не выбирает алгоритм только потому, что увидел его в недоверенном header. Он получает допустимый набор из собственной конфигурации, связывает ключ с ожидаемым issuer и проверяет криптографическую операцию. Затем он сопоставляет iss, aud, exp, nbf и другие обязательные для этого протокола claims. Особенно важно не применять одинаковые правила к ID Token, access token и другим JWT, иначе корректно подписанный объект одного типа может быть принят в месте, для которого он не предназначался.

OAuth не требует JWT, а OpenID Connect требует его для ID Token

JWT часто появляется рядом с OAuth 2.0, но эти понятия нельзя использовать как синонимы. OAuth определяет делегирование доступа и не закрепляет единственный формат access token. Сервер авторизации может выдать непрозрачную строку, которую resource server проверит через хранилище или introspection, либо самодостаточный JWT, валидируемый локально. RFC 9068 стандартизирует профиль второго варианта, но не отменяет первый.

Более того, OAuth-клиент должен относиться к access token как к непрозрачному значению, даже если строка сейчас похожа на JWT. Формат является договорённостью между authorization server и resource server и может измениться; интерфейс приложения не должен строить поведение на самостоятельном чтении claims из access token.

В OpenID Connect роли разделены яснее. ID Token обязательно является JWT и сообщает клиенту о событии аутентификации пользователя. Access token предназначен для обращения к API и может иметь другой формат, а refresh token используется для получения новых access token и тоже не обязан быть JWT. ID Token нельзя отправлять в API вместо access token только потому, что обе строки могут выглядеть одинаково.

Cookie описывает, как браузер хранит небольшое значение и автоматически прикладывает его к подходящим запросам. JWT описывает структуру claims. Серверная сессия, в свою очередь, означает, что значимая часть состояния находится на сервере, а клиент обычно хранит случайный идентификатор. Эти механизмы находятся на разных уровнях: JWT можно передавать в Authorization или cookie, а содержимым cookie может быть как JWT, так и session ID.

Локальная проверка JWT позволяет resource server получить claims без запроса к центральному хранилищу, но это не делает систему полностью stateless. Для немедленного отзыва, актуальной проверки прав, ротации ключей и обнаружения повторного использования всё равно может понадобиться серверное состояние. Непрозрачный идентификатор добавляет сетевую или серверную проверку, зато облегчает централизованный отзыв и не раскрывает claims клиенту.

Универсального места хранения для браузерного токена тоже нет. Архитектура Backend for Frontend может оставить OAuth-токены на сервере, выдав браузеру защищённую HttpOnly, Secure и подходящим образом настроенную SameSite cookie с идентификатором сессии. JavaScript не сможет прочитать такую cookie, но серверу всё равно нужна защита от CSRF, а XSS может выполнять запросы от имени пользователя, пока вредоносный код работает на странице.

Если браузерное приложение получает access token напрямую, хранение в памяти сокращает время его доступности, но не переживает перезагрузку страницы. localStorage удобен для сохранения, однако любой скрипт, выполнившийся в том же origin после XSS, способен прочитать его содержимое. Актуальный RFC 10017 рассматривает эти варианты через конкретные угрозы, а не объявляет одно хранилище безопасным для всех приложений.

Кража bearer token не зависит от его формата

Обычный OAuth access token является bearer credential: сторона, которая им владеет, может предъявить его resource server. Если такой токен украден, формат JWT или opaque сам по себе ничего не меняет. Возможный ущерб уменьшают короткий срок действия, минимальные scope, точный audience, TLS, защита хранилища и механизмы отзыва. Sender-constrained токены дополнительно требуют доказать владение ключом, поэтому одной скопированной строки для их повторного использования недостаточно.

Refresh token обычно живёт дольше и позволяет выпускать новые access token, поэтому последствия его утечки серьёзнее. RFC 9700 требует для публичных клиентов ротацию refresh token или криптографическую привязку к отправителю. Эти меры относятся к безопасности OAuth, а не к внутреннему устройству JWT.

Что проверяет JWT Inspector

JWT Inspector работает локально в браузере и предназначен для компактных JWS из трёх сегментов. Он декодирует JSON header и payload, показывает числовые claims iat, exp, nbf и auth_time как даты и отдельно отмечает истёкший или ещё не вступивший в силу токен.

Для проверки MAC инструмент принимает обычный или Base64URL-секрет и поддерживает HS256, HS384 и HS512. Для асимметричной проверки можно вставить открытый PEM-ключ; доступны RS256/384/512, PS256/384/512 и ES256/384/512. Все операции выполняет Web Crypto API, токен и введённый ключ не отправляются на сервер. JWE инструмент не расшифровывает, а alg: none показывает как неподписанный режим без возможности проверки.

Важно различать результат инструмента и полную валидацию. Декодирование ничего не говорит о доверии, а сообщение о корректной подписи подтверждает совпадение только с тем ключом, который ввёл пользователь. Inspector не получает доверенный ключ по metadata или JWKS, не проверяет iss, aud, тип токена, scope и правила приложения. Даже зелёная отметка exp опирается на одно временное поле и не превращает содержимое в допустимый access token или ID Token.

Для безопасной проверки используйте тестовый токен, выберите ключ из доверенного источника и заранее определите ожидаемые алгоритм, issuer, audience и назначение. Inspector поможет увидеть структуру и отдельно проверить криптографическую операцию, но окончательное решение должен принимать валидатор того протокола, в котором токен используется.

Похожие инструменты

Частые вопросы

JWT всегда подписан, но не зашифрован?

Нет. JWT описывает набор claims, который может быть защищён структурой JWS или зашифрован в структуре JWE. Чаще всего разработчик встречает подписанный или снабжённый MAC компактный JWS из трёх сегментов, где header и payload читаются без ключа. Компактный JWE состоит из пяти сегментов, а его claims находятся внутри ciphertext. Стандарт допускает и unsecured JWT с alg=none, но принимать его можно только в заранее определённом закрытом сценарии.

Чем декодирование JWT отличается от проверки и валидации?

Декодирование лишь превращает Base64URL в читаемые header и claims. Проверка подписи или MAC подтверждает, что защищённые байты не менялись и были обработаны обладателем соответствующего ключа. Полная валидация дополнительно проверяет разрешённый алгоритм, доверенный источник ключа, issuer, audience, сроки, тип токена и правила конкретного приложения.

OAuth access token всегда имеет формат JWT?

Нет. OAuth не задаёт обязательный формат access token: он может быть непрозрачной случайной строкой или JWT. RFC 9068 определяет профиль JWT access token, но даже в этом случае клиенту не следует строить логику на чтении его claims. В OpenID Connect именно ID Token обязан быть JWT, поскольку он сообщает клиенту сведения о событии аутентификации.

JWT заменяет серверную сессию и cookie?

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

Что произойдёт при краже JWT access token?

Если это обычный bearer access token, одного владения им достаточно для обращения к API, независимо от того, имеет он формат JWT или opaque. Ущерб ограничивают коротким сроком действия, узкими scope и audience, безопасным хранением и отзывом; для некоторых систем применяют привязку токена к ключу отправителя. Refresh token требует ещё более строгой защиты и обычно использует rotation или sender constraint.

Автор

Fullstack Engineer

В коммерческой разработке с 2011 года — больше 15 лет. Автор двух SaaS-продуктов: TeleGo.io и LingvoHabit.com. Статьи здесь выросли из реальной клиентской и продуктовой работы.

Подробнее об автореНаписать в Telegram

Похожие статьи

NUMBER BASE CONVERTER
NUMBER BASE CONVERTER · '26

Как компьютер представляет числа: биты, hex и IEEE 754

Почему одно число записывают как 45, 101101 и 2D, откуда взялись двоичная, восьмеричная и hex-системы и чем IEEE 754 отличается от них.

#Number Base Converter #IEEE 754 #двоичная система #шестнадцатеричная система #дополнительный код #римские цифры #ивритская гематрия #греческие цифры #история математики
5 мин. чтения
ИСТОРИЯ ФОРМАТИРОВАНИЯ КОДА
ИСТОРИЯ ФОРМАТИРОВАНИЯ КОДА · '26

Почему разработчики до сих пор спорят о форматировании

Краткая история соглашений о форматировании и точные границы beautify/minify для HTML и CSS в нашем локальном браузерном инструменте.

#история форматирования кода #Prettier #indent style #K&R vs Allman #код-стайл #табы против пробелов
2 мин. чтения
GZIP VS BROTLI
GZIP VS BROTLI · '26

Как патенты на LZW привели к gzip, а веб к Brotli

История gzip, DEFLATE и Brotli, устройство двух форматов сжатия и практический выбор между размером результата, скоростью кодирования и совместимостью.

#GZip vs Brotli #история сжатия данных #Jean-loup Gailly #Brotli Google #Deflate алгоритм #LZ77 Huffman
5 мин. чтения