JWT — как безопасный способ аутентификации и передачи данных
JSON Web Token (JWT) — это открытый стандарт (RFC 7519) для создания токенов доступа, основанный на формате JSON. Как правило, используется для передачи данных для аутентификации в клиент-серверных приложениях. Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в дальнейшем использует данный токен для подтверждения своей личности.
В простом понимании — это строка в специальном формате, которая содержит данные, например, ID и имя зарегистрированного пользователя. Она передается при каждом запросе на сервер, когда необходимо идентифицировать и понять, кто прислал этот запрос.
В этой статье разберу, что такое Access токен, Refresh токен и как с ними работать.
Для дальнейших разборов будет использован токен:
После того, как посетитель прошел авторизацию в нашей системе, указав свой логин и пароль, система выдает ему 2 токена: access token и refresh токен.
После чего посетитель, когда хочет получить с сервера данные, например, свой профиль, вместе с запросом он передает Access токен, как на примере выше. Сервер, получив его проверяет, что он действительный (об этом чуть ниже), вычитывает полезные данные из него (тот же user_id) и, таким образом, может идентифицировать пользователя.
Токен разделен на три основные группы: заголовок, полезные данные и сигнатура, разделенные между собой точкой.
Это можно проверить прям в браузере, выполнив в консоле или js коде:
Вторым блоком идет eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9
Это есть полезные данные, так же закодированные в Base64. После раскодирования получим:
Поскольку необходимо ограничивать токен по времени, поле exp обязательно. По нему можно проверить, актуален ли токен или нет.
Она получается примерно следующим образом:
Берем заголовок, например <"alg":"HS256","typ":"JWT">и кодируем его в base64, получаем ту самую часть eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
Тоже самое проделываем с данными eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9
После этого склеиваем их и получаем eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9
Далее эти данные шифруем с помощью нашего алгоритма HMAC-SHA256 и ключа.
Для проверка токена необходимо проделать ту же операцию.
Как только время выйдет, пользователю снова придется проходить авторизацию. Так вот чтобы этого избежать, существует Refresh токен. С помощью него можно продлить Access токен.
У него, обычно, нет какой-то структуры и это может быть некая случайная строка.
Для проекта odo24.ru я использовал следующий подход.
Генерируется Access токен и после случайная строка, например T6cjEbghMZmybUd_fhE
С нашего нового Access токена eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE1ODEzNTcwMzl9.E4FNMef6tkjIsf7paNrWZnB88c3WyIfjONzAeEd4wF0 беру последние шесть знаков, получаю Ed4wF0
Склеиваю и получаю рефреш токен T6cjEbghMZmybUd_fhEEd4wF0
Это сделано для привязки Access токена к Refresh. Для получения новых токенов необходимо передать эти два токена. Делается проверка на их связку и только после валидируется Access токен. Если и второй этап прошел успешно, тогда получаем с базы данных по текущему user_id рефреш токен и сверяем с тем, что к нам пришел. Если они совпадают, тогда генерируются новые токены и в базе данных обновляется Refresh токен на новый.
В моем случае я разделил оба токена и храню в разных местах. Access токен нужен только для идентификации пользователя и на клиенте (JS) он не нужен, поэтому он передается в Cookie (http only).
Refresh токен хранится в LocalStorage и используется только когда Access токен перестал быть актуальным.
Представим ситуацию, когда у нас каким-то образом украли Access токен. Да, это уже плохо и где-то у нас брешь в безопасности. Злоумышленник в этом случае сможет им воспользоваться не более чем на 15-30 минут. После чего токен «протухнет» и перестанет быть актуальным. Ведь нужен второй токен для продления.
Если украли Refresh токен, то без Access токена (который недоступен в JS) продлить ничего нельзя и он оказывается просто бесполезным.
Дату протухания внедрил прям в токен с той целью, чтобы не хранить эту информацию где-то в другом месте, например, в базе данных.
Дата содержит год, месяц, день, час и минуты. Хранится в ASCII
Кодирование даты на Golang:
Всю реализацию на Go можно изучить на Github-е
В этой статье попытался рассказать о взаимодействии двух токенов и как ими пользоваться. В сети достаточно много информации о Access токенах, однако мало, как мне показалось, информации о Refresh токенах.
Шпаргалки по безопасности: JWT
Многие приложения используют JSON Web Tokens (JWT), чтобы позволить клиенту идентифицировать себя для дальнейшего обмена информацией после аутентификации.
JSON Web Token – это открытый стандарт (RFC 7519), который определяет компактный и автономный способ безопасной передачи информации между сторонами в виде объекта JSON.
Эта информация является проверенной и надежной, потому что она имеет цифровую подпись.
JWT могут быть подписаны с использованием секретного (с помощью алгоритма HMAC) или пары открытого / секретного ключей с использованием RSA или ECDSA.
JSON Web Token используется для передачи информации, касающейся личности и характеристик клиента. Этот «контейнер» подписывается сервером, чтобы клиент не вмешивался в него и не мог изменить, например, идентификационные данные или какие-либо характеристики (например, роль с простого пользователя на администратора или изменить логин клиента).
Этот токен создается в случае успешной аутентификации и проверяется сервером перед началом выполнения каждого клиентского запроса. Токен используется приложением в качестве “удостоверения личности” клиента (контейнер со всей информацией о нем). Сервер же имеет возможность проверять действительность и целостность токена безопасным способом. Это позволяет приложению быть stateless (stateless приложение не сохраняет данные клиента, сгенерированные за один сеанс для использования в следующем сеансе с этим клиентом (каждый сеанс выполняется независимо)), а процессу аутентификации независимым от используемых сервисов (в том смысле, что технологии клиента и сервера могут различаться, включая даже транспортный канал, хотя наиболее часто используется HTTP).
Соображения по поводу использования JWT
Даже если токен JWT прост в использовании и позволяет предоставлять сервисы (в основном REST) без сохранения состояния (stateless), такое решение подходит не для всех приложений, потому что оно поставляется с некоторыми оговорками, как, например, вопрос хранения токена.
Если приложение не должно быть полностью stateless, то можно рассмотреть возможность использования традиционной системы сессий, предоставляемой всеми веб-платформами. Однако для stateless приложений JWT – это хороший вариант, если он правильно реализован.
Проблемы и атаки, связанные с JWT
Использование алгоритма хеширования NONE
Подобная атака происходит, когда злоумышленник изменяет токен, а также меняет алгоритм хеширования (поле “alg”), чтобы указать через ключевое слово none, что целостность токена уже проверена. Некоторые библиотеки рассматривали токены, подписанные с помощью алгоритма none, как действительный токен с проверенной подписью, поэтому злоумышленник мог изменить полезную нагрузку (payload) токена, и приложение доверяло бы токену.
Для предотвращения атаки необходимо использовать библиотеку JWT, которая не подвержена данной уязвимости. Также во время проверки валидности токена необходимо явно запросить использование ожидаемого алгоритма.
Перехват токенов
Атака происходит, когда токен был перехвачен или украден злоумышленником и он применяет его для получения доступа к системе, используя идентификационные данные определенного пользователя.
Защита заключается в добавлении «пользовательского контекста» в токен. Пользовательский контекст будет состоять из следующей информации:
Если во время проверки токена полученный токен не содержит правильного контекста, он должен быть отклонен.
Пример реализации:
Код для создания токена после успешной аутентификации:
Код для проверки валидности токена:
Явное аннулирование токена пользователем
Поскольку токен становится недействительным только после истечения срока его действия, у пользователя нет встроенной функции, позволяющей явно отменить действие токена. Таким образом, в случае кражи пользователь не может сам отозвать токен и затем заблокировать атакующего.
Одним из способов защиты является внедрение черного списка токенов, который будет пригоден для имитации функции «выход из системы», существующей в традиционной системе сеансов.
В черном списке будет храниться сборник (в кодировке SHA-256 в HEX) токена с датой аннулирования, которая должна превышать срок действия выданного токена.
Когда пользователь хочет «выйти», он вызывает специальную службу, которая добавляет предоставленный токен пользователя в черный список, что приводит к немедленному аннулированию токена для дальнейшего использования в приложении.
Хранилище черного списка:
Для централизованного хранения черного списка будет использоваться база данных со следующей структурой:
Управление аннулированиями токенов:
Раскрытие информации о токене
Эта атака происходит, когда злоумышленник получает доступ к токену (или к набору токенов) и извлекает сохраненную в нем информацию (информация о токене JWT кодируется с помощью base64) для получения информации о системе. Информация может быть, например, такой как, роли безопасности, формат входа в систему и т.д.
Способ защиты достаточно очевиден и заключается в шифровании токена. Также важно защитить зашифрованные данные от атак с использованием криптоанализа. Для достижения всех этих целей используется алгоритм AES-GCM, который обеспечивает аутентифицированное шифрование с ассоциированными данными (Authenticated Encryption with Associated Data – AEAD). Примитив AEAD обеспечивает функциональность симметричного аутентифицированного шифрования. Реализации этого примитива защищены от адаптивных атак на основе подобранного шифртекста. При шифровании открытого текста можно дополнительно указать связанные данные, которые должны быть аутентифицированы, но не зашифрованы.
То есть шифрование с соответствующими данными обеспечивает подлинность и целостность данных, но не их секретность.
Однако необходимо отметить, что шифрование добавляется в основном для сокрытия внутренней информации, но очень важно помнить, что первоначальной защитой от подделки токена JWT является подпись, поэтому подпись токена и ее проверка должны быть всегда использованы.
Хранение токенов на стороне клиента
Если приложение хранит токен так, что возникает одна или несколько из следующих ситуаций:
Остается случай, когда злоумышленник использует контекст просмотра пользователя в качестве прокси-сервера, чтобы использовать целевое приложение через легитимного пользователя, но Content Security Policy может предотвратить связь с непредвиденными доменами.
Также возможно реализовать службу аутентификации таким образом, чтобы токен выдавался внутри защищенного файла cookie, но в этом случае должна быть реализована защита от CSRF.
Использование слабого ключа при создании токена
Если секрет, используемый в случае алгоритма HMAC-SHA256, необходимый для подписи токена, является слабым, то он может быть взломан (подобран c помощью атаки грубой силы). В результате злоумышленник может подделать произвольный действительный токен с точки зрения подписи.
Для предотвращения этой проблемы надо использовать сложный секретный ключ: буквенно-цифровой (смешанный регистр) + специальные символы.
Поскольку ключ необходим только для компьютерных вычислений, размер секретного ключа может превышать 50 позиций.
Для оценки сложности секретного ключа, используемого для вашей подписи токена, вы можете применить атаку по словарю паролей к токену в сочетании с JWT API.
JWT простым языком: что такое JSON токены и зачем они нужны
Краткий, но исчерпывающий обзор JWT и его возможностей. JSON токены, их структура, построение и распространенные способы использования.
После беглого знакомства с JSON web tokens может сложиться впечатление, что они встроены в современные механизмы авторизации и аутентификации, такие как OAuth или OpenID. Однако это не совсем так. JSON токены действительно используются в этих системах, но не являются их частью. Более того, сфера их использование гораздо шире авторизации.
Эта статья посвящена детальному разбору JWT и его возможностей. Мы изучим структуру токена и построим его с нуля. Затем рассмотрим наиболее распространенные способы использования, затронув попутно тему серверных и клиентских сеансов. Наконец, доберемся до криптографических функций безопасности, которые и делают JSON токены важным звеном в процессах авторизации.
Что такое JWT?
Если обратиться с этим запросом в Google, вероятнее всего, он выдаст что-то подобное:
Все эти определения верны, но они звучат чересчур научно и абстрактно. Попробуем дать JWT собственное описание.
Веб-токен JSON, или JWT (произносится «jot»), представляет собой стандартизированный, в некоторых случаях подписанный и/или зашифрованный формат упаковки данных, который используется для безопасной передачи информации между двумя сторонами.
Проанализируем эту формулировку.
Формат упаковки данных
JWT определяет особую структуру информации, которая отправляется по сети. Она представлена в двух формах – сериализованной и десериализованной. Первая используется непосредственно для передачи данных с запросами и ответами. С другой стороны, чтобы читать и записывать информацию в токен, нужна его десериализация.
Десериализованная форма
В несериализованном виде JWT состоит из заголовка и полезной нагрузки, которые являются обычными JSON-объектами.
Заголовок (заголовок JOSE) в основном используется для описания криптографических функций, которые применяются для подписи и/или шифрования токена. Здесь также можно указать дополнительные свойства, например, тип содержимого, хотя это редко требуется. Чтобы узнать больше о заявках заголовка, обратитесь к спецификации.
Если JWT подписан и/или зашифрован, в заголовке указывается имя алгоритма шифрования. Для этого предназначена заявка alg :
Заявки бывают служебными и пользовательскими. Первые обычно являются частью какого-либо стандарта, например, реестра JSON Web Token Claims, и имеют определенные значения. Наиболее распространенные служебные заявки:
Полный список заявок приведен в реестре.
Неподписанные JSON токены
Заголовок описывает криптографические операции, которые применяются к веб-токену. Но в некоторых случаях подпись и шифрование могут отсутствовать. Обычно это происходит, когда JWT является частью некоторой уже зашифрованной структуры данных. В заголовке такого неподписанного токена заявка alg должна быть равна none :
Полезная нагрузка
В спецификации OpenID можно ознакомиться с полным списком заявок.
Сериализованные JSON токены
JSON web token в сериализованной форме – это строка следующего формата:
[ Header ].[ Payload ].[ Signature ]
Заголовок (header) и полезная нагрузка (payload) присутствуют всегда, а вот подпись (signature) может отсутствовать.
Пример компактной формы:
Она получена из следующих данных:
Сериализация
На рисунке изображен процесс сериализации неподписанного токена:
В коде это можно представить следующим образом:
Чтобы декодировать токен, достаточно просто разбить его по точкам и конвертировать заголовок и полезную нагрузку из кода base64url обратно в строку. Пример кода, который это делает:
Использование библиотек
Разумеется, JSON токены не кодируются вручную. Существует множество библиотек, предназначенных для этого. Например, jsonwebtoken:
Этот код создает подписанный JWT с использованием секретного слова. Затем он проверяет подлинность токена и декодирует его, применяя тот же секрет. Подпись и другие механизмы безопасности будут разобраны далее.
Приложения
Пора переходить к практическому применению JWT. В принципе, JSON токены может использовать любой процесс, связанный с обменом данных через сеть. Например, простое клиент-серверное приложение или группа из нескольких связанных серверов и клиентов. Отличным примером сложных процессов со множеством потребителей данных служат фреймворки авторизации, такие как AuthO и OpenID.
Чаще всего используются клиентские сеансы без сохранения состояния. При этом вся информация размещается на стороне клиента и передается на сервер с каждым запросом. Именно здесь используется JSON web token, который обеспечивает компактный и защищенный контейнер для данных.
Чтобы понять принцип работы токенов, нужно разобраться в концепции клиентских сеансов. Для этого вспомним о традиционных серверных сессиях и узнаем, почему же произошел переход на сторону клиента.
Серверные сессии с хранением состояния
Как известно, HTTP – протокол без учета состояния. Это означает, что он не обеспечивает механизм для объединения нескольких запросов одного клиента. Тем не менее, большинство приложений нуждается в такой функциональности. Чтобы предоставлять пользователям индивидуальный опыт, необходимо отслеживать некоторую информацию, например, учетные данные или список товаров в корзине покупателя.
Многие годы для этого использовались сеансы на стороне сервера. Пользовательские данные при этом хранятся в файловой системе, базе данных или memcache. Вот для примера список различных сессионных хранилищ известного модуля Node.js express-session.
Чтобы связать данные на сервере с клиентскими запросами, идентификатор сеанса помещается в файлы cookie или сохраняется с помощью других HTTP-функций. Он отправляется на сервер с каждым запросом и используется для поиска сеанса этого конкретного клиента. Важный момент: в файлах cookie сохраняется только идентификатор, но не сама информация.
Данные сеанса представляют собой определенное состояние, поэтому мы говорим о сессиях с хранением состояния.
Реализация серверных сеансов – дело непростое. Наличие состояний затрудняет репликацию и исправление ошибок. Самым большим недостатком серверных сессий является то, что их трудно масштабировать. Необходимо либо дублировать данные одного сеанса на всех серверах, либо использовать центральное хранилище, либо гарантировать, что данный пользователь всегда попадает на один и тот же сервер. Любой из этих подходов связан с серьезными сложностями.
Все эти затруднения побудили программистов искать альтернативы. Одним из очевидных решений является хранение данных пользователя на клиенте, а не на сервере. В настоящее время этот подход широко используется. Он известен как клиентские сессии без хранения состояния.
Сеансы на стороне клиента
В отличие от серверных сессий, данные клиентских сеансов хранятся на машине пользователя и отправляются с каждым запросом. Один из способов реализации этого механизма – использование файлов cookie. В них можно хранить не только идентификатор сеанса, но и сами данные. Куки-файлы очень удобно использовать, поскольку они автоматически обрабатываются веб-браузерами. Однако это не единственный способ передавать информацию сеанса. Это также можно делать через HTTP-заголовки, чтобы избежать проблем с CORS, или с помощью URL-параметров.
С сеансами на стороне клиента снимается проблема с масштабируемостью. Однако появляются уязвимости безопасности. Нельзя гарантировать, что клиент не изменяет данные сессии. Например, если идентификатор пользователя хранится в файлах cookie, его легко изменить. Это позволит получить доступ к чужой учетной записи. Чтобы предотвратить подобные действия, нужно обернуть данные в защищенный от несанкционированного воздействия пакет.
Именно для этого и нужен JWT. Благодаря тому, что JSON токены имеют подпись, сервер может проверять подлинность данных сеанса и доверять им. При необходимости их можно даже зашифровать, чтобы защититься от чтения и изменения.
Впрочем, у сеансов на стороне клиента также есть свои минусы. Например, приложение может требовать большого объема пользовательских данных, и их придется отправлять туда-обратно для каждого запроса. Это может даже перекрыть все преимущества простоты и масштабируемости. Таким образом, в реальном мире приложения зачастую совмещают клиентские и серверные сеансы.
Защита веб-токенов
Как уже упоминалось выше, JWT использует два механизма для защиты информации: подписи и шифрование. Их описывают стандарты безопасности JSON Web Signature (JWS) и JSON Web Encryption (JWE).
Подпись JWT
Цель подписи заключается в том, чтобы дать возможность одной или нескольким сторонам установить подлинность токена. Помните пример подделки идентификатора пользователя из cookie для получения доступа к чужой учетной записи? Токен можно подписать, чтобы проверить, не были ли изменены данные, содержащиеся в нем. Однако подпись не мешает другим сторонам читать содержимое JWT, это уже дело шифрования. Подписанный веб-токен известен как JWS (JSON Web Signature). В компактной сериализованной форме у него появляется третий сегмент – подпись.
Самый распространенный алгоритм подписи – HMAC. Он объединяет полезную нагрузку с секретом, используя криптографическую хеш-функцию (чаще всего SHA-256). С помощью полученной уникальной подписи можно верифицировать данные. Это схема называется разделением секрета, поскольку он известен обеим сторонам: создателю и получателю. Таким образом, и тот, и другой могут генерировать новое подписанное сообщение.
Другой алгоритм подписи – RSASSA. В отличие от HMAC он позволяет принимающей стороне только проверять подлинность сообщения, но не генерировать его. Алгоритм основан на схеме открытого и закрытого ключей. Закрытый ключ может использоваться как для создания подписанного сообщения, так и для проверки. Открытый ключ, напротив, позволяет лишь проверить подлинность. Это важно во многих сценариях подписки, таких как Single-Sign On, где есть только один создатель сообщения и много получателей. Таким образом, никакой злонамеренный потребитель данных не сможет их изменить.
Шифрование
В отличие от подписи, которая является средством установления подлинности токена, шифрование обеспечивает его нечитабельность.
Зашифрованный JWT известен как JWE (JSON Web Encryption). В отличие от JWS, его компактная форма имеет 5 сегментов, разделенных точкой. Дополнительно к зашифрованному заголовку и полезной нагрузке он включает в себя зашифрованный ключ, вектор инициализации и тег аутентификации.
Подобно JWS, он может использовать две криптографические схемы: разделение секрета и открытый/закрытый ключи.
Схема разделенного секрета аналогична механизму подписки. Все стороны знают секрет и могут шифровать и дешифровать токен.
Однако схема закрытого и открытого ключей работает по-другому. Все владельцы открытых ключей могут шифровать данные, но только сторона, владеющая закрытым ключом, может расшифровать их. Получается, что в этом случает JWE не может гарантировать подлинность токена. Чтобы иметь гарантию подлинности, следует использовать совмещать его с JWS.
Это важно только в ситуациях, когда потребитель и создатель данных являются разными сущностями. Если это один объект, тогда JWT, зашифрованный по схеме разделенного секрета, предоставляет те же гарантии, что и сочетание шифрования с подписью.
Json web token что это
В последнее время все чаще можно встретить приложения, использующие для аутентификации пользователей механизмы JSON Web Tokens. Особую популярность JWT завоевал с ростом популярности микросервисной архитектуры: он возлагает задачу по обработке аутентификационных данных на сами микросервисы, а следовательно позволяет избежать различных ошибок авторизации, увеличить производительность и улучшить масштабируемость приложения.
Вместе с тем неправильное использование JWT может негативно сказаться на безопасности приложения. Мы приведем примеры использования JWT, разберем распространенные ошибки в реализации схем аутентификации с применением JWT, рассмотрим основные виды атак на эти схемы и дадим рекомендации по их предотвращению.
Формат JWT: описание
В этом разделе статьи мы расскажем, что такое JSON Web Tokens, из каких частей он состоит, как используется для аутентификации пользователей и в чем заключается преимущество JWT в сравнении с классической схемой аутентификации с использованием сессий.
Структура JWT
Согласно RFC-7519, JSON Web Tokens — один из способов представления данных для передачи между двумя или более сторонами в виде JSON-объекта.
Как правило, структурно JWT состоит из трех частей:
Бывают и исключения, когда в JWT отсутствует подпись. Подобный случай будет рассмотрен далее.
Заголовок и полезная нагрузка — обычные JSON-объекты, которые необходимо дополнительно закодировать при помощи алгоритма base64url. Закодированные части соединяются друг с другом, и на их основе вычисляется подпись, которая также становится частью токена.
В общем случае токен выглядит следующим образом:
На рис. 1 можно увидеть, что токен состоит из трех частей, разделенных точками.

Красная часть — заголовок:
Фиолетовая часть — полезная нагрузка:
Голубая часть — подпись:
Рассмотрим структуру полей более подробно.
Заголовок
Заголовок — служебная часть токена. Он помогает приложению определить, каким образом следует обрабатывать полученный токен.
Эта часть, как было ранее упомянуто, является JSON-объектом и имеет следующий формат:
Здесь присутствуют следующие поля:
Значение поля typ зачастую игнорируется приложениями, однако стандарт не рекомендует отказываться от него для обеспечения обратной совместимости.
Поле alg обязательно для заполнения. В приведенном случае был применен алгоритм HS256 (HMAC-SHA256), в котором для генерации и проверки подписи используется единый секретный ключ.
Для подписи JWT могут применяться и алгоритмы асимметричного шифрования, например RS256 (RSA-SHA256). Стандарт допускает использование и других алгоритмов, включая HS512, RS512, ES256, ES512, none и др.
Использование алгоритма none указывает на то, что токен не был подписан. В подобном токене отсутствует часть с подписью, и установить его подлинность невозможно.
Полезная нагрузка
В полезной нагрузке передается любая информация, которая помогает приложению тем или иным образом идентифицировать пользователя. Дополнительно могут передаваться определенные служебные поля, однако они не обязательны для заполнения, поэтому на них останавливаться мы не будем.
В нашем случае полезная нагрузка содержит следующий JSON-объект:
Здесь присутствуют следующие поля:
Поскольку набор полей в части полезной нагрузки произвольный, приложение может хранить в этой части практически любые данные. Например, для ускорения работы приложения в полезной нагрузке могут храниться Ф. И. О. пользователя, чтобы не запрашивать эти сведения каждый раз из базы данных.
Подпись
Подпись генерируется следующим образом.
Заголовок и полезная нагрузка кодируются при помощи алгоритма base64url, после чего объединяются в единую строку с использованием точки ( «.» ) в качестве разделителя.
Генерируется подпись (в нашем примере — с применением алгоритма HMAC-SHA256), которая добавляется к исходной строке так же через точку.
На псевдокоде алгоритм выглядит примерно так:
Получив JWT от пользователя, приложение самостоятельно вычислит значение подписи и сравнит его с тем значением, которое было передано в токене. Если эти значения не совпадут, значит, токен был модифицирован или сгенерирован недоверенной стороной, и принимать такой токен и доверять ему приложение не будет.
Подпись приведенного в пример токена можно проверить с использованием секретного ключа test (например, на сайте jwt.io).
Аутентификация с использованием JWT
Схема аутентификации с использованием JWT предельно проста.
Пользователь вводит свои учетные данные в приложении или доверенном сервисе аутентификации. При успешной аутентификации сервис предоставляет пользователю токен, содержащий сведения об этом пользователе (уникальный идентификатор, Ф. И. О., роль и т. д.).
При последующих обращениях токен передается приложению в запросах от пользователя (в cookie, заголовках запроса, POST- или GET-параметрах и т. д.).
Получив токен, приложение сперва проверяет его подпись. Убедившись, что подпись действительна, приложение извлекает из части полезной нагрузки сведения о пользователе и на их основе авторизует его.
Преимущества JWT
Перечислим преимущества использования JWT в сравнении с классической схемой аутентификации, использующей сессии.
Во-первых, подход с использованием токенов позволяет не хранить информацию обо всех выданных токенах, как при классической схеме. Когда пользователь обращается к приложению, он передает ему свой токен. Приложению остается только проверить подпись и извлечь необходимые поля из полезной нагрузки.
Во-вторых, приложению вообще не обязательно заниматься выдачей и валидацией токенов самостоятельно, зачастую для этих целей используется отдельный сервис аутентификации.
В-третьих, при использовании отдельного сервиса аутентификации становится возможным организовать единую точку входа в различные сервисы с одними и теми же учетными данными. Единожды пройдя процедуру аутентификации, пользователь сможет получить доступ со своим токеном к тем ресурсам, которые доверяют этому сервису аутентификации.
В-четвертых, как было указано ранее, приложение может хранить в части полезной нагрузки практически любые данные, что при грамотной архитектуре приложения может существенно увеличить производительность.
Благодаря перечисленным факторам схема аутентификации с использованием JWT широко используется в различных корпоративных приложениях. Особенно популярна эта схема в тех приложениях, которые реализуют парадигмы микросервисной архитектуры: при таком подходе каждый сервис получает необходимые ему сведения о пользователе непосредственно из токена, а не тратит время на получение этой информации из базы данных.
Формат JWT: атаки
В этом разделе будут рассмотрены основные атаки на JWT и даны рекомендации по их предотвращению.
Перехват токена
Перехват пользовательского токена может привести к ряду неприятных последствий.
Во-первых, так как JWT передается в открытом виде, для получения хранящихся в части полезной нагрузки исходных данных достаточно применить к этой части функцию base64UrlDecode. То есть злоумышленник, перехвативший токен, сможет извлечь хранящиеся в токене данные о пользователе.
В соответствии с лучшими практиками для предотвращения подобной угрозы рекомендуется:
Во-вторых, злоумышленник, перехвативший токен, сможет его переиспользовать и получить доступ к приложению от лица пользователя, чей JWT был перехвачен.
Здесь рекомендации будут следующие:
Refresh tokens
В современных схемах аутентификации, основанных на JWT, после прохождения аутентификации пользователь получает два токена:
Access token при таком подходе имеет сильно ограниченное время жизни (например, одну минуту). Refresh token же имеет длительное время жизни (день, неделя, месяц), но он одноразовый и служит исключительно для обновления access token пользователя.
Схема аутентификации в таком случае выглядит следующим образом:
Подбор ключа симметричного алгоритма подписи
При использовании симметричных алгоритмов для подписи JWT (HS256, HS512 и др.) злоумышленник может попытаться подобрать ключевую фразу.
Подобрав ее, злоумышленник получит возможность манипулировать JWT-токенами так, как это делает само приложение, а следовательно сможет получить доступ к системе от лица любого зарегистрированного в ней пользователя.
Рекомендации для защиты от атаки в этом случае такие:
Использование алгоритма none
Как было упомянуто в первой части статьи, использование в заголовке JWT алгоритма none указывает на то, что токен не был подписан. В подобном токене отсутствует часть с подписью, и установить его подлинность становится невозможно.
Рассмотрим подобную атаку на нашем примере. Наш токен в незакодированном виде выглядит следующим образом:
Предположим, мы хотим, чтобы приложение считало нас администратором. Для этого необходимо установить значение admin в поле role полезной нагрузки. Но при внесении в токен этого изменения подпись токена станет невалидной, и приложение не примет такой JWT.
Для обхода защитного механизма мы можем попытаться изменить значение поля alg в заголовке токена на none. Наш токен примет следующий вид:
Поскольку мы используем алгоритм none, подпись отсутствует. В закодированном виде наш JWT будет выглядеть так:
Этот токен мы и передадим на сервер. Уязвимое приложение, проверив заголовок JWT и обнаружив в нем alg: none, примет этот токен без всяких проверок, как если бы он был легитимным, в результате чего мы получим привилегии администратора.
Чтобы защититься от такой атаки:
Изменение алгоритма подписи
При использовании асимметричных алгоритмов подпись токена осуществляется с использованием приватного ключа сервиса, а проверка подписи — с использованием публичного ключа сервиса.
Некоторые реализации библиотек для работы с JWT содержат логические ошибки, заключающиеся в том, что при получении токена, подписанного с использованием симметричного алгоритма (например, HS256), для проверки подписи в качестве ключевой фразы будет использован публичный ключ сервиса. Поскольку публичный ключ сервиса не засекречен, злоумышленник может легко получить его и использовать для подписи собственных токенов.
Для рассмотрения примера этого варианта атаки нам понадобится новый JWT:
В кодированном виде он будет выглядеть следующим образом:
Поскольку в этом случае мы используем для подписи алгоритм RS256, нам понадобятся публичный и приватный ключи.
Для тестов мы будем использовать сайт jwt.io (рис. 2).

Как и в предыдущем примере, модифицируем токен:
В кодированном виде заголовок и полезная нагрузка будут выглядеть следующим образом:
Остается только подсчитать подпись с использованием публичного ключа сервиса.
Для начала переводим ключ в hex-представление (рис. 3).

Затем генерируем подпись с использованием openSSL (рис. 4).

Полученное значение E1R1nWNsO-H7h5WoYCBnm6c1zZy-0hu2VwpWGMVPK2g добавляем к уже имеющейся строке, и наш токен принимает следующий вид:
Подставляем в поле secret на jwt.io наш публичный ключ, и JWT успешно проходит проверку (не забудьте поставить галочку secret base64 encoded!) (рис. 5).

Для предотвращения такой атаки рекомендуется:
Манипуляция ключевыми идентификаторами
Стандарт RFC-7515 описывает параметр заголовка kid (Key ID, идентификатор ключа). Вместе с тем стандарт говорит о том, что формат этого поля строго не определен. Поэтому разработчики вольны интерпретировать его так, как удобно им, что зачастую приводит к различным ошибкам.
Для примера возьмем следующий заголовок JWT:
Предполагается, что для проверки токена будет использован хранящийся в БД ключ с идентификатором 1337. В случае ошибок кодирования это поле может быть уязвимо к SQL-инъекции:
В таком случае для проверки подписи ключа в качестве ключевой фразы вместо предполагаемого ключа из базы данных будет использована строка SECRET_KEY.
В следующем примере предположим, что для проверки токена будет использован ключ из файла keys/service3.key.
Если параметр не валидируется, злоумышленник сможет провести атаку Path Traversal (Directory Traversal) и вместо предполагаемого пути до файла с ключом передаст в поле kid путь до какого-либо публичного файла:
Злоумышленник может получить доступ к файлу cat.png и подписать JWT с использованием содержимого этого файла, поскольку этот файл общедоступный (например, он опубликован на одной из страниц сервиса). Сервис же, получив в поле kid путь до файла cat.png, использует его содержимое в качестве ключевого файла для проверки подписи токена (которая окажется успешной, ведь злоумышленник заранее об этом позаботился).
Рекомендация по предотвращению подобных атак простая: необходимо всегда валидировать и санитизировать полученные от пользователя данные, даже если они были получены в виде JWT.
Заключение
JSON Web Tokens — популярная и удобная технология. При правильном использовании JWT избавляет от распространенных ошибок недостаточной авторизации, позволяет просто и удобно распределить информационные потоки между сервисами, организовать единую точку входа в различные сервисы с одними и теми же учетными данными и даже повысить производительность сервиса.
Вместе с тем при неправильном использовании JWT можно подвергнуть свою систему существенным рискам, вплоть до компрометации учетных записей абсолютно всех пользователей системы.
Итак, для безопасного использования JWT следует:






