JWT для чайников: 5 шагов к пониманию JSON веб-токенов
Что такое JWT, с чем его едят, и как он обеспечивает безопасность вашего приложения? Пошаговое руководство для понимания JSON веб-токенов с нуля.
JWT играет важную роль в веб-безопасности и системах аутентификации пользователей, хотя на первый взгляд ничего особенного из себя не представляет – обычная строка. Впрочем, не совсем обычная, как мы сейчас увидим.
У JSON веб-токенов есть четко определенная структура, определенная стандартом RFC 7519:
header.payload.signature
- Заголовок (
header
) и полезная нагрузка (payload
) – это всего лишь объекты в формате JSON, содержащие определенные данные (в том числе технические). - Подпись (
signature
) чуть сложнее. Она создается на основе заголовка и полезной нагрузки, кодируется особым образом и в целом реализует основную функцию токена – идентификацию пользователя.
Возьмем для примера классическую схему работы приложения:
Что у нас тут есть?
- некоторое приложение,
- пользователь, который желает с приложением взаимодействовать,
- сервер аутентификации, который должен подтвердить, что пользователь – именно тот, за кого он себя выдает.
Схема работы очень простая (в теории, по крайней мере):
- Сначала пользователь проходит привычную аутентификацию. Это может быть пара логин-пароль или вход через какую-нибудь соцсеть (Facebook, VK).
- Если все прошло удачно, сервер аутентификации создает JWT и отправляет его пользователю обратно. Как именно происходит генерация JSON веб-токенов, мы разберем через минуту.
- Теперь пользователь пытается взаимодействовать с приложением через API. Делая запрос, он передает также полученный токен.
- Сервер приложения проверяет, действительно ли этот токен был выдан сервером аутентификации – о реализации этой проверки мы тоже поговорим.
- Если 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 очень быстро станет недействительным.