Введение: Что такое JWT и зачем он нужен?
JSON Web Token (JWT), произносится как “джот” (/dʒɒt/), представляет собой открытый стандарт (RFC 7519) для создания токенов доступа, которые используются для безопасной передачи информации между двумя сторонами в виде JSON-объекта. В мире веб-разработки JWT стали де-факто стандартом для реализации аутентификации и авторизации в API, особенно в микросервисной архитектуре и одностраничных приложениях (SPA).
Основная идея JWT заключается в создании “самодостаточного” токена. Это означает, что вся необходимая информация для проверки пользователя (например, его идентификатор и права доступа) содержится внутри самого токена. Когда клиент отправляет запрос на сервер, он прикрепляет к нему этот токен. Сервер, получив токен, может проверить его подлинность и, если проверка прошла успешно, предоставить доступ к запрашиваемому ресурсу. Такой подход избавляет сервер от необходимости хранить информацию о сессиях в базе данных, что упрощает масштабирование и повышает производительность системы.
Основные концепции: Структура JWT
JWT состоит из трех частей, разделенных точками (.):
xxxxx.yyyyy.zzzzz
- Заголовок (Header)
- Полезная нагрузка (Payload)
- Подпись (Signature)
Давайте рассмотрим каждую часть подробнее.
1. Заголовок (Header)
Заголовок — это JSON-объект, который содержит метаданные о токене. Как правило, он состоит из двух полей:
typ(Type): Тип токена, который всегда имеет значение “JWT”.*alg(Algorithm): Алгоритм хеширования, используемый для создания подписи. Наиболее распространенными являютсяHS256(HMAC с использованием SHA-256) иRS256` (RSA с использованием SHA-256).
Пример заголовка:
{
"alg": "HS256",
"typ": "JWT"
} Этот JSON-объект кодируется в строку формата Base64Url и образует первую часть JWT.
2. Полезная нагрузка (Payload)
Полезная нагрузка — это JSON-объект, содержащий так называемые “заявки” (claims). Заявки — это утверждения о сущности (обычно о пользователе) и дополнительные данные. Существует три типа заявок:
Зарезервированные (Registered claims): Это стандартные, предопределенные заявки, которые не являются обязательными, но рекомендуются к использованию. Некоторые из них:
iss(Issuer): Идентификатор издателя токена.sub(Subject): Идентификатор “темы” токена (например, ID пользователя).aud(Audience): Идентификаторы получателей токена.exp(Expiration Time): Время истечения срока действия токена (в формате Unix time).iat(Issued At): Время создания токена (в формате Unix time).).
Публичные (Public claims): Эти заявки определяются теми, кто использует JWT. Чтобы избежать коллизий, их имена должны быть уникальными. Обычно для этого используют URI.
Приватные (Private claims): Это пользовательские заявки, созданные для обмена информацией между сторонами, которые договорились об их использовании. Они не являются ни зарезервированными, ни публичными.
Пример полезной нагрузки:
{
"sub": "1234567890",
"name": "John Doe",
"admin": true,
"iat": 1516239022
} Важно: Полезная нагрузка, как и заголовок, кодируется в Base64Url. Это означает, что любой, кто перехватит токен, сможет легко прочитать его содержимое. Поэтому никогда не храните в полезной нагрузке конфиденциальную информацию, такую как пароли.
3. Подпись (Signature)
Подпись используется для проверки того, что токен не был изменен по пути от издателя к получателю. Она создается путем объединения закодированных заголовка и полезной нагрузки, секретного ключа и последующего хеширования с использованием алгоритма, указанного в заголовке.
Псевдокод для создания подписи с использованием HS256:
signature = HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
) Если используется асимметричный алгоритм, такой как RS256, подпись создается с помощью приватного ключа, а проверяется с помощью публичного.
Практические примеры
Пример 1: Схема аутентификации
Ниже представлена диаграмма, иллюстрирующая типичный процесс аутентификации с использованием JWT.
sequenceDiagram
participant User
participant AppServer
participant AuthServer
User->>AuthServer: Запрос на аутентификацию (логин/пароль)
AuthServer->>AuthServer: Проверка учетных данных
alt Успешная аутентификация
AuthServer->>AuthServer: Создание JWT
AuthServer-->>User: Возврат JWT
else Неудачная аутентификация
AuthServer-->>User: Ошибка аутентификации
end
User->>AppServer: Запрос к защищенному ресурсу + JWT в заголовке Authorization
AppServer->>AppServer: Проверка подписи и срока действия JWT
alt Валидный токен
AppServer-->>User: Возврат запрошенного ресурса
else Невалидный токен
AppServer-->>User: Ошибка 401 Unauthorized
end Пример 2: Создание и проверка JWT на Python
Для работы с JWT в Python можно использовать библиотеку PyJWT. Сначала установим ее:
pip install PyJWT Создание токена:
import jwt
import datetime
# Секретный ключ (в реальном приложении должен быть сложным и храниться в секрете)
SECRET_KEY = "your-super-secret-key"
# Данные для полезной нагрузки
payload = {
"user_id": 123,
"username": "testuser",
"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=1), # Срок жизни - 1 час
"iat": datetime.datetime.utcnow()
}
# Создание JWT
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
print(f"Сгенерированный токен: {token}") Проверка токена:
import jwt
# Предположим, это токен, полученный от клиента
received_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxMjMsInVzZXJuYW1lIjoidGVzdHVzZXIiLCJleHAiOjE2NzQ0ODQyMjMsImlhdCI6MTY3NDQ4MDYyM30.some_signature"
SECRET_KEY = "your-super-secret-key"
try:
# Декодирование и проверка токена
decoded_payload = jwt.decode(received_token, SECRET_KEY, algorithms=["HS256"])
print("Токен валиден!")
print(f"Полезная нагрузка: {decoded_payload}")
except jwt.ExpiredSignatureError:
print("Срок действия токена истек.")
except jwt.InvalidTokenError:
print("Невалидный токен.") Типичные ошибки и как их избежать
- Х1.Хранение токенов в
localStorage:** Это делает их уязвимыми для XSS-атак. Если злоумышленник сможет выполнить свой JavaScript-код на вашей странице, он получит доступ к токену. Более безопасной альтернативой является хранение токенов вHttpOnlycookies. Это предотвращает доступ к ним из JavaScript. - 2.Использование слабых секретных ключей:** Секретный ключ должен быть длинным, сложным и случайным. Не используйте очевидные строки вроде “secret” или “password”.
- 3.Отсутствие срока действия (
exp):** Токены без срока действия могут быть использованы злоумышленником бесконечно, если они будут скомпрометированы. Всегда устанавливайте короткий срок жизни для токенов доступа (access tokens) и используйте токены обновления (refresh tokens) для получения новых. - П4.Передача конфиденциальной информации в
payload:** Как уже упоминалось,payloadне шифруется. Не храните в нем пароли, личные данные или другую чувствительную информацию.
Связь с другими темами
JWT часто используется в связке с OAuth 2.0. В этом сценарии JWT может выступать в роли токена доступа (access token). Когда клиентское приложение получает от сервера авторизации токен доступа в формате JWT, оно может использовать его для аутентификации запросов к серверу ресурсов. Сервер ресурсов, в свою очередь, может проверить токен локально, без обращения к серверу авторизации, что является одним из ключевых преимуществ использования JWT.
Заключение
JWT — это мощный и гибкий инструмент для реализации аутентификации и авторизации в современных веб-приложениях. Он позволяет создавать масштабируемые и производительные системы за счет своей “самодостаточности” и отсутствия необходимости хранить сессии на сервере. Однако, как и любой инструмент, связанный с безопасностью, JWT требует внимательного и правильного использования. Понимание его структуры, принципов работы и потенциальных уязвимостей является ключом к созданию надежных и защищенных приложений.