JWT для чайников: 5 шагов к пониманию JSON веб-токенов

Что такое JWT, с чем его едят, и как он обеспечивает безопасность вашего приложения? Пошаговое руководство для понимания JSON веб-токенов с нуля.

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

У JSON веб-токенов есть четко определенная структура, определенная стандартом RFC 7519:

header.payload.signature
  • Заголовок (header) и полезная нагрузка (payload) – это всего лишь объекты в формате JSON, содержащие определенные данные (в том числе технические).
  • Подпись (signature) чуть сложнее. Она создается на основе заголовка и полезной нагрузки, кодируется особым образом и в целом реализует основную функцию токена – идентификацию пользователя.

Возьмем для примера классическую схему работы приложения:

Что у нас тут есть?

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

Схема работы очень простая (в теории, по крайней мере):

  1. Сначала пользователь проходит привычную аутентификацию. Это может быть пара логин-пароль или вход через какую-нибудь соцсеть (Facebook, VK).
  2. Если все прошло удачно, сервер аутентификации создает JWT и отправляет его пользователю обратно. Как именно происходит генерация JSON веб-токенов, мы разберем через минуту.
  3. Теперь пользователь пытается взаимодействовать с приложением через API. Делая запрос, он передает также полученный токен.
  4. Сервер приложения проверяет, действительно ли этот токен был выдан сервером аутентификации – о реализации этой проверки мы тоже поговорим.
  5. Если JWT подлинный, сервер просто выполняет полученный запрос и пользователь получает нужный ему результат.

Создание JSON веб-токенов в 5 шагов

Шаг 1. Заголовок

В заголовке JWT содержатся технические данные – название алгоритма, используемого для генерации подписи токена. Выглядит он вот так:

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

Это самый обычный JSON-объект.

Поле typ содержит тип токена (для JSON веб-токенов соответственно оно всегда равно JWT).

Значение поля alg соответствует алгоритму хеширования HMAC-SHA256, который использует секретный ключ для вычисления подписи (подробнее в шаге 3). Здесь может быть указан другой алгоритм или даже none, если токен не подписан.

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

Во втором компоненте JSON веб-токенов (payload) хранится информация о пользователе, которую сервер аутентификации передает серверу приложения. Стандарт предусматривает несколько необязательных для заполнения служебных полей, например:

  • exp – срок окончания действия токена;
  • nbf – время начала действия токена;
  • sub – уникальный идентификатор пользователя.

Если интересно, почитайте более подробное описание компонента полезной нагрузки в Википедии.

Помимо стандартных ключей, можно передавать любые данные:

{
  "userId": "b08f86af-35da-48f2-8fab-cef3904660bd"
}

Это вновь простой JSON-объект, ничего нового.

Помните, что размер данных полезной нагрузки влияет на общий размер JSON веб-токенов. Если их будет слишком много, передача токена может ухудшить производительность вашего приложения.

Шаг 3. Подпись

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

data = base64urlEncode(header) + "." + base64urlEncode(payload)
hashedData = hash(data, secret)
signature = base64urlEncode(hashedData)

Разберем по шагам:

  • Заголовок и полезная нагрузка по отдельность кодируются с помощью алгоритма Base64URL, а затем соединяются через точку. Вы можете поиграть с этим алгоритмом в онлайн-конвертере. На выходе получается обычная строка
// header
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

// payload
eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhLTQ4ZjItOGZhYi1jZWYzOTA0NjYwYmQifQ
  • Эта строка затем хешируется с использованием секретного ключа (secret). Конкретный алгоритм хеширования определяется в заголовке JSON веб-токенов, как мы уже видели.
  • Хешированные данные снова пропускаются через Base64URL – это и есть подпись JWT.
// signature
-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

Шаг 4. Cборка JWT

У нас уже есть все необходимые блоки для создания токена:

  • заголовок с указанием алгоритма хеширования;
  • полезная нагрузка с пользовательскими данными;
  • сгенерированная подпись.

Теперь можно собрать из них полноценный веб-токен по уже знакомой схеме:

header.payload.signature

Заголовок и полезная нагрузка предварительно кодируются в Base64URL, а подпись уже в правильном формате:

// JWT
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1nij9.eyJ1c2VySWQiOiJiMDhmODZhZi0zNWRhltq4zjitogzhyi1jzwyzota0njywymqifq.-xN_h82PHVTCMA9vdoHrcZxH-x5mb11y1537t3rGzcM

Этот токен пользователь получит от сервера аутентификации и будет использовать при запросах к серверу приложения.

Узнайте больше о JSON веб-токенах и создайте собственный JWT на jwt.io.

Шаг 5. Верификация JWT

В демо-примере мы использовали хеширование с секретным ключом по алгоритму HS256. Изначально этот ключ известен только серверу аутентификации. Сервер приложения получает его при настройке процесса проверки подлинности.

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

Пара слов о безопасности данных

Теперь, когда вы разобрались в устройстве и работе JSON веб-токенов, возникает естественный вопрос – защищены ли передаваемые данные?

Мы видели, что токен кодируется и подписывается, но – внимание! – не шифруется! (в чем разница между кодированием и шифрованием?) То есть эта обработка не обеспечивает безопасность данных. Нет никакой гарантии сохранности, поэтому не следует передавать в токенах что-то конфиденциальное. У JWT другое предназначение: проверка подлинности источника данных. Это дополнительный уровень безопасности самого приложения.

P.S.

Мы разобрали основы JSON веб-токенов, но в этой теме еще много нюансов.

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

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

Хорошей практикой является установка небольшого срока действия JSON веб-токенов. При этом скомпрометированный JWT очень быстро станет недействительным.

Мы что-то упустили? Поделитесь в комментариях ;)

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ