Что такое JWT на самом деле
JSON Web Token — это строка. В этом весь фокус. Вместо того чтобы выдать клиенту идентификатор сессии и держать саму сессию на сервере, вы выдаёте ему подписанное утверждение — «это пользователь 4821, вот его права, всё это истекает в 14:05» — и позволяете его носить с собой. Любой сервис, доверяющий ключу подписи, прочитает это утверждение и примет решение, не обращаясь к базе данных.
Именно поэтому JWT одновременно очень удобны и слегка опасны: данные находятся в руках клиента. Выданный вами токен прочитает любой, у кого он есть, отозвать его до истечения срока нельзя, и его примет всё, что доверяет подписи. Почти все проблемы с JWT в продакшене возникают из-за того, что один из этих трёх фактов забыли.
Токен выглядит как три блока непонятных символов, разделённые точками:
eyJhbGciOiJIUzI1NiIsImtpZCI6ImtleS0yIn0.eyJzdWIiOiI0ODIxIiwiZXhwIjoxNzY1MTExMTExfQ.3xS7_c9Nn-aQ
Он не зашифрован. Первые два блока — это обычный JSON в Base64url, безопасном для URL алфавите,
где + и / заменены на - и _, а завершающие
= отброшены. Любой превратит их обратно в JSON за секунду — именно это и делает
декодер JWT.
Три части и назначение каждой
Разбейте токен по точкам — получите заголовок, нагрузку и подпись.
Заголовок
Маленький и почти всегда про ключи. alg называет алгоритм подписи,
typ обычно равен JWT, а kid — идентификатор ключа, который
говорит проверяющей стороне, какой открытый ключ забрать, если у издателя их несколько. Если токен
внезапно перестал проходить проверку после ротации ключей, смотрите сначала на
kid.
Полезная нагрузка
Клеймы — собственно само утверждение. Часть из них зарегистрирована спецификацией
(iss, sub, aud, exp, nbf,
iat, jti), остальное — то, что решил добавить ваш издатель:
scope, roles, tenant_id, адрес почты, флаг функции.
Подпись
MAC или цифровая подпись по первым двум частям — ровно в том виде, в каком они стоят в строке. Эта деталь важна: подпись покрывает закодированные байты, поэтому если пересобрать JSON и закодировать заново, получится токен, который больше не проходит проверку, даже если данные те же. Никогда не пересобирайте токен, чтобы «привести его в порядок».
Как декодировать токен и на что смотреть первым делом
Вставьте строку целиком в декодер, вместе с точками. Если вы скопировали её из запроса, уберите
префикс Bearer — или возьмите инструмент, который уберёт его сам. Дальше читайте в
таком порядке:
- Вообще декодировалось? Если декодер говорит, что части — не корректный JSON в Base64url, токен пришёл обрезанным или испорченным. Так выглядят и кука, упёршаяся в лимит размера, и заголовок, разорванный переносом строки.
exp. Истечение срока объясняет большинство жалоб «пять минут назад работало», и исключать надо в первую очередь именно его.audиiss. Токен, выпущенный для другого получателя, — совершенно корректный токен, который ваш API правильно отклоняет.- Собственные клеймы. Спорить о том, то ли лежит в
scope, стоит только после того, как стандартные клеймы сошлись.
Декодируйте локально, где можете. Токен — это действующий доступ, поэтому вставить его на случайный сайт — ошибка того же класса, что вставить туда пароль: любой нормальный инструмент декодирует в браузере и никуда ничего не отправляет.
Клеймы, от которых зависит, сработает ли токен
Почти всю работу делают пять клеймов, и каждый из них способен отклонить запрос сам по себе.
exp— срок действия, метка Unix в секундах. Значение в миллисекундах здесь — классическая ошибка: оно попадает в 57000 год, и токен не истекает никогда. Хороший декодер показывает и настоящую дату, и сколько времени прошло.nbf— not before. До этого момента токен недействителен. Намеренно используется редко, иногда выставляется случайно на машине с убежавшими вперёд часами.iat— время выпуска. Полезно, чтобы отклонять формально не истёкшие, но подозрительно старые токены, и чтобы заметить проблему с часами, когдаiatв будущем.aud— получатель, для кого токен предназначен. API, который пропускает эту проверку, с радостью примет токен, выпущенный вашим же другим сервисом для себя, а это реальный путь повышения привилегий между микросервисами.iss— издатель. Жёстко закрепите ожидаемого издателя и берите ключи только из опубликованного им набора.
jti стоит особняком: это уникальный идентификатор токена. Он что-то значит только
если вы его храните и сверяете, но именно он делает возможным отзыв одного конкретного токена.
Декодировать — не значит проверить
Вот место, где спотыкаются чаще всего. Декодирование читает Base64url и показывает JSON. Оно говорит, что токен утверждает. И ничего не говорит о том, правда ли это.
Любой может написать {"sub":"1","role":"admin"}, закодировать в
Base64url, приписать в конец произвольную строку вместо подписи и отдать вам. В любом декодере,
включая наш, это раскодируется идеально. Работать этому не даёт шаг проверки: пересчитать подпись
ключом издателя и отвергнуть токен, если она не совпала. Для этого шага нужен секрет или открытый
ключ, его место — на вашем сервере, и ни один браузерный инструмент не сделает его за вас.
Поэтому относитесь к декодеру как к показаниям прибора, а не как к доказательству. «Я декодировал, там написано admin» — это не находка по безопасности. «Мой сервер проверил подпись, и там написано admin» — да.
Почему с виду правильный токен получает 401
Если токен декодируется чисто, а API всё равно его не принимает, почти всегда дело в одном из этого списка:
- Истёк. Сверяйте
expс часами сервера, а не своими. - Расхождение часов. Сервер, убежавший на пару минут вперёд, отвергнет только что выпущенные токены. Большинство библиотек допускают небольшой запас именно поэтому; если у вас он нулевой, это стоит пересмотреть.
- Не тот получатель. Частое дело, когда один провайдер идентификации обслуживает несколько API.
- Ротация ключей. Издатель подписал новым ключом, у проверяющей стороны в кеше
ещё старый набор, и
kidбольше не находится. - Токен вообще не дошёл. Отсутствующий или искажённый заголовок
Authorization, потерянная кука или прокси, вырезающий заголовки. Посмотрите, что сервер реально получил, прежде чем винить токен: поиск HTTP-кодов состояния и наше руководство по кодам состояния помогают отличить 401 от 403, который означает совсем другое.
401 или 403?
401 значит, что доступа нет, он истёк или не проверяется: исправляйте токен.
403 значит, что доступ был в порядке, а ответ всё равно «нет»: исправляйте права или
клейм scope. Сервисы, отдающие 401 при провале авторизации, отправляют людей
отлаживать токен, который никогда и не был проблемой.
Как выбрать и проверить алгоритм подписи
На практике важны два семейства. HS256 — это HMAC с общим секретом: кто может проверить, тот может и подписать, что нормально внутри одного сервиса и плохо подходит между организациями. RS256 и ES256 подписывают закрытым ключом, а проверяют открытым, поэтому API может проверять токены, которые сам выпустить бы не смог.
И есть alg: none — неподписанный токен, который спецификация разрешает и который
принимать нельзя никогда. Он есть в стандарте, значит был в библиотеках, значит его
эксплуатировали: проверяющей стороне, берущей алгоритм из заголовка самого токена, можно заявить
«этот не подписан, просто поверь». Родственный приём подсовывает проверяющему на RS256 токен
HS256, подписанный открытым ключом в роли секрета HMAC.
Защита — одна строчка политики: решите, какие алгоритмы вы принимаете, до того как
взглянуть на токен, и отвергайте всё остальное. Заголовок — это подсказка о том, какой
ключ взять, а не указание, проверять ли подпись. Если ваш декодер помечает
alg: none красным, причина в этом.
Чего в нагрузке быть не должно
Поскольку нагрузка закодирована, а не зашифрована, каждый клейм открыт любому, у кого есть токен, — а токены оказываются в хранилище браузера, в логах, в системах сбора ошибок, в тикетах поддержки и на скриншотах. Не кладите в нагрузку:
- Ничего из того, что вы не поставили бы в URL: пароли, ключи API, номера карт, медицинские данные.
- Внутренние идентификаторы, которые не хотелось бы публиковать: первичные ключи базы, внутренние имена хостов, условия лицензии.
- Большие блоки данных. Токен едет с каждым запросом; килобайт клеймов — это килобайт на каждый вызов, а раздутые токены надёжно упираются в лимиты размера заголовков на прокси.
Держите токены и подальше от URL. Токен в строке запроса попадает в логи сервера, в заголовки
Referer и в историю браузера. Передавайте его в заголовке
Authorization и убедитесь, что сайт задаёт разумный Referrer-Policy: об
этом рассказывает наше руководство по
заголовкам безопасности, а проверка
заголовков безопасности покажет, что вы отдаёте сегодня.
Срок действия, обновление и проблема отзыва
JWT нельзя вернуть обратно. Выпущенный токен действует до exp, что бы ни случилось
с аккаунтом за ним. Отключите пользователя — его токен доступа будет работать, пока не кончится
срок. Это цена отказа от обращения к базе на каждом запросе.
Стандартный ответ — два токена. Короткоживущий токен доступа (минуты, не дни) несёт клеймы и делает работу. Долгоживущий токен обновления хранится на сервере, сверяется с базой при каждом использовании и отзывается мгновенно. Выход из системы тогда означает аннулировать токен обновления и подождать короткое окно, пока токен доступа умрёт сам.
Выбирайте окно осознанно: это разрыв между отзывом доступа и фактическим прекращением доступа.
Пятнадцать минут — распространённый компромисс. Часы оправдать трудно; дни означают, что отзыва у
вас нет вовсе. А если нужен мгновенный отзыв, ведите список запрещённых значений
jti — и вот вы снова обращаетесь к хранилищу на каждом запросе, и обычная сессия может
оказаться проще по устройству.
Что бы вы ни выбрали, цикл отладки один и тот же: декодировать токен, прочитать
exp и aud, сверить часы, затем проверить подпись на сервере. Четыре шага
в этом порядке — и почти любая проблема с токеном раскрывается на первых двух.
Связанные инструменты и руководства:
- Декодер JWT — Декодируйте JSON Web Token и прочитайте заголовок, нагрузку, клеймы и срок действия.
- Кодировщик и декодер Base64 — Кодируйте текст в Base64 или обратно — с URL-safe выводом и корректным UTF-8.
- Base64 простыми словами: как работают кодирование и декодирование — Как Base64 превращает байты в текст
Частые вопросы
Можно ли декодировать JWT без секретного ключа?⌄
Да. Заголовок и нагрузка закодированы в Base64url, а не зашифрованы, поэтому их прочитает любой, у кого есть токен. Ключ нужен только для проверки подписи. Именно поэтому в нагрузку нельзя класть чувствительные данные.
Безопасно ли вставлять JWT в онлайн-декодер?⌄
Только если декодирование происходит в вашем браузере. До истечения срока токен — действующий доступ, так что инструмент, отправляющий его на сервер, работает с вашими учётными данными. Наш декодер работает целиком в странице и никуда токен не отправляет, но самая безопасная привычка — по возможности брать истёкшие или тестовые токены.
Почему токен декодируется нормально, но всё равно возвращается 401?⌄
Декодирование доказывает лишь, что строка правильно устроена. Обычные причины: уже прошедший exp, расхождение часов между издателем и проверяющей стороной, aud с другим API или kid, который проверяющая сторона не может разрешить после ротации ключей.
Чем JWT отличается от сессионной куки?⌄
Сессионная кука — это указатель: сервер держит состояние и смотрит его на каждом запросе, поэтому отзыв мгновенный. JWT несёт состояние сам, поэтому обращаться никуда не нужно — и ничего нельзя забрать до истечения срока. JWT лучше масштабируется между сервисами; сессии дают контроль.
Что выбрать, HS256 или RS256?⌄
HS256 использует один общий секрет, поэтому кто может проверить, тот может и подписать. Внутри одного сервиса это допустимо. RS256 и ES256 подписывают закрытым ключом и проверяют открытым — именно это нужно, как только участников больше одного. В любом случае закрепите список принимаемых алгоритмов на стороне проверки и никогда не доверяйте значению alg из токена.
Более 15 лет практического опыта в веб-разработке и безопасности. Специализация — практическая веб-безопасность: чтобы сайты были защищёнными, надёжными и легко диагностировались. BuildToolForge создан, чтобы такие проверки были бесплатными, приватными и понятными. Инструменты по возможности работают прямо в браузере, а всё, что вы вводите в инструмент, не сохраняется.
Как мы пишем руководства: Каждое руководство основано на практическом опыте, сверено с официальными стандартами и проверено нашими инструментами на реальных доменах. Мы обновляем руководства, когда меняются стандарты или инструменты. Нашли ошибку? Напишите нам: contact@buildtoolforge.com.