Payload и header в JWT: полное руководство по структуре и защите
#Веб-разработка #Веб-безопасность #КибербезопасностьДля кого эта статья:
- Опытные веб-разработчики
- Специалисты по информационной безопасности
- Архитекторы программного обеспечения и систем
JWT (JSON Web Token) стал стандартом де-факто для передачи заявлений между сервисами в мире веб-разработки. Однако за кажущейся простотой скрывается сложная архитектура и множество потенциальных уязвимостей, о которых не подозревают даже опытные разработчики. Что происходит внутри этих токенов? Каждый JWT хранит критически важную информацию в payload и метаданные в header, но правильная структуризация и защита этих элементов — это тонкое искусство, требующее глубоких технических знаний. В этом руководстве мы препарируем JWT до атомов, рассмотрим каждый элемент структуры и раскроем приёмы, которые защитят ваши приложения от распространённых атак. 🔐
Архитектура JWT: структура header, payload и подписи
JWT представляет собой строку, состоящую из трёх частей, разделённых точками: header.payload.signature. Эта строка кодируется в формате Base64URL, что делает её безопасной для передачи в URL-параметрах и HTTP-заголовках.
Структура JWT напоминает сэндвич, где каждый слой выполняет определённую функцию:
- Header (заголовок) — метаданные о типе токена и используемом алгоритме подписи
- Payload (полезная нагрузка) — набор утверждений (claims) о пользователе или других сущностях
- Signature (подпись) — криптографическая подпись, гарантирующая целостность первых двух частей
Рассмотрим пример JWT-токена:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
При декодировании первой части (header) получаем JSON-объект:
{
"alg": "HS256",
"typ": "JWT"
}
Вторая часть (payload) после декодирования даёт нам:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Третья часть — это HMACSHA256 подпись, созданная по формуле:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Важно понимать, что header и payload в JWT не зашифрованы, а только закодированы с помощью Base64URL. Любой, кто перехватит токен, может декодировать и прочитать эти части. Безопасность обеспечивается только подписью, которая гарантирует, что токен не был изменён.
Алексей, руководитель отдела безопасности
Недавно наша команда столкнулась с серьёзной проблемой утечки данных. Разработчики использовали JWT для передачи чувствительной информации между микросервисами, не понимая, что payload не шифруется. В токенах передавались персональные данные пользователей, включая номера телефонов и адреса. Достаточно было перехватить один такой токен и декодировать его с помощью любого онлайн-инструмента.
Мы провели срочный аудит и перепроектировали архитектуру: теперь в payload хранятся только идентификаторы и минимум необходимой информации, а чувствительные данные запрашиваются отдельно по защищённым каналам. Также внедрили шифрование (JWE) для особо важных токенов. Этот случай стал отличным уроком для всей команды о том, как важно понимать внутреннюю структуру используемых технологий.
| Компонент JWT | Описание | Уровень защиты | Рекомендации |
|---|---|---|---|
| Header | Метаданные о токене | Не защищён (только кодирование Base64URL) | Не включать чувствительную информацию |
| Payload | Полезная нагрузка с claims | Не защищён (только кодирование Base64URL) | Минимизировать объём и чувствительность данных |
| Signature | Криптографическая подпись | Защищён (криптографические алгоритмы) | Использовать надёжные алгоритмы и хранить секрет в безопасности |

Поля JWT header: алгоритмы и типы токенов
Header JWT содержит информацию о том, как должен обрабатываться токен. Обязательными полями являются:
- alg (algorithm) — используемый алгоритм подписи или шифрования
- typ (type) — тип токена, обычно "JWT"
Кроме этих базовых полей, header может включать дополнительные параметры в зависимости от конкретных требований:
- kid (Key ID) — идентификатор ключа, используемого для подписи
- x5c (X.509 Certificate Chain) — цепочка сертификатов X.509
- jku (JWK Set URL) — URL для получения набора JSON Web Keys
- cty (Content Type) — тип содержимого вложенного объекта
Алгоритмы подписи JWT делятся на несколько категорий:
| Категория | Алгоритмы | Особенности | Рекомендации по использованию |
|---|---|---|---|
| HMAC | HS256, HS384, HS512 | Используют симметричный ключ | Для систем с одним сервером валидации |
| RSA | RS256, RS384, RS512 | Используют пару ключей (приватный/публичный) | Для распределённых систем, где токены создаются и проверяются разными сервисами |
| ECDSA | ES256, ES384, ES512 | Эллиптические кривые, меньшие ключи при той же безопасности | Для систем с ограничениями производительности или размера токена |
| EdDSA | EdDSA | Современный алгоритм цифровой подписи Edwards-curve | Для новых систем, требующих высокой безопасности |
| Отсутствие подписи | none | Токен без подписи | Никогда не использовать в продакшене |
При выборе алгоритма подписи необходимо учитывать несколько факторов:
- Безопасность — современные алгоритмы предпочтительнее устаревших.
- Производительность — HMAC быстрее RSA, а ECDSA быстрее при той же безопасности.
- Размер токена — подписи RSA длиннее, чем ECDSA.
- Архитектура системы — для микросервисов предпочтительны асимметричные алгоритмы.
Пример правильно сформированного заголовка JWT с использованием RS256:
{
"alg": "RS256",
"typ": "JWT",
"kid": "2021-12-key-1"
}
Обратите внимание на поле kid — оно особенно полезно при ротации ключей, позволяя системе определить, какой именно ключ использовать для проверки подписи. 🔑
Payload JWT: стандартные и кастомные claims
Payload JWT, или тело токена — это JSON-объект, содержащий утверждения (claims) о сущности (обычно о пользователе) и дополнительные метаданные. Claims подразделяются на три типа:
- Registered Claims — предопределенные стандартом JWT
- Public Claims — определяемые пользователями, но зарегистрированные в IANA JSON Web Token Claims Registry
- Private Claims — специфические для конкретного приложения, по соглашению между участниками
Стандартные (Registered) claims имеют трехбуквенные идентификаторы и определены в спецификации RFC 7519:
- iss (issuer) — идентификатор стороны, создавшей токен
- sub (subject) — идентификатор субъекта токена (обычно пользователя)
- aud (audience) — получатель(и) токена
- exp (expiration time) — время истечения срока действия токена
- nbf (not before) — время, до которого токен не должен приниматься
- iat (issued at) — время создания токена
- jti (JWT ID) — уникальный идентификатор токена
Пример payload с стандартными claims:
{
"iss": "https://auth.example.com",
"sub": "user123",
"aud": "https://api.example.com",
"exp": 1617054000,
"iat": 1616967600,
"jti": "756E69717565206964656E746966696572"
}
Кастомные (Private) claims могут содержать любую информацию, необходимую для конкретного приложения. Например:
{
"user_id": 123456,
"name": "Александр",
"role": "admin",
"permissions": ["read", "write", "delete"],
"department": "IT",
"office_location": "Moscow"
}
При проектировании payload важно соблюдать принцип минимальных привилегий и включать только необходимую информацию. Поскольку payload легко декодируется, никогда не включайте в него:
- Пароли или хеши паролей
- Секретные ключи API
- Конфиденциальные личные данные (номера паспортов, карт и т.д.)
- Информацию, которая может часто меняться
Для оптимизации размера токена используйте короткие имена claims и минимизируйте включаемые данные. Помните, что JWT передаётся с каждым запросом, поэтому его размер влияет на производительность.
Если требуется включение чувствительной информации, рассмотрите использование JWT Encryption (JWE) вместо или в дополнение к подписанному JWT (JWS). 📦
Критические уязвимости JWT и методы их устранения
Несмотря на кажущуюся простоту, JWT-токены подвержены нескольким серьезным уязвимостям, которые могут полностью скомпрометировать систему аутентификации. Рассмотрим основные атаки и методы защиты от них:
- Атака с алгоритмом None
Суть атаки: злоумышленник изменяет алгоритм в header с, например, HS256 на "none", удаляет подпись и пытается использовать модифицированный токен. Если сервер неправильно реализован и принимает токены без подписи, это позволяет создать действительный токен с любым содержимым.
Защита:
- Явно отклоняйте токены с алгоритмом "none".
- Используйте библиотеки, которые правильно проверяют алгоритм подписи.
- Белый список разрешенных алгоритмов на сервере.
- Атака с заменой алгоритма
Суть атаки: злоумышленник меняет алгоритм с асимметричного (например, RS256) на симметричный (например, HS256). Если сервер использует публичный ключ RSA как секрет для HMAC, это позволяет подделать токен.
Защита:
- Жестко задавайте ожидаемый алгоритм при проверке.
- Используйте разные переменные для хранения симметричных и асимметричных ключей.
- Не полагайтесь на алгоритм, указанный в токене.
- Слабый секретный ключ
Суть атаки: при использовании слабого секретного ключа для HMAC (HS256, HS384, HS512) злоумышленник может подобрать ключ методом грубой силы и подделать токен.
Защита:
- Используйте криптографически стойкие ключи длиной не менее 256 бит.
- Для генерации ключей используйте специализированные инструменты, а не придумывайте вручную.
- Регулярно производите ротацию ключей.
- Атака на время жизни токена
Суть атаки: если сервер не проверяет claims exp, nbf или iat, злоумышленник может использовать просроченные токены или создавать токены с очень длительным сроком действия.
Защита:
- Обязательно проверяйте время действия токена (exp).
- Устанавливайте разумный срок действия (от минут до часов, а не дней или месяцев).
- Используйте механизм обновления токенов (refresh tokens) для долгосрочной аутентификации.
- Небезопасное хранение токенов на клиенте
Суть атаки: хранение JWT в локальном хранилище (localStorage) делает его уязвимым для атак XSS.
Защита:
- Используйте HttpOnly cookies для хранения токенов.
- Применяйте флаги Secure и SameSite для cookies.
- Внедрите защиту от CSRF для cookies.
Михаил, пентестер
При проведении аудита безопасности крупного финтех-стартапа я обнаружил критическую уязвимость в их реализации JWT. Разработчики использовали библиотеку, которая не проверяла корректность алгоритма подписи. Я смог изменить алгоритм в header с RS256 на HS256 и подписать токен, используя публичный ключ как HMAC-секрет.
Это позволило мне создать токен с правами администратора и получить полный доступ к системе. Самое интересное, что разработчики были уверены в безопасности своей реализации, потому что "используют проверенную библиотеку". Они не понимали, что неправильно её настроили, пропустив параметр с явным указанием ожидаемого алгоритма.
После моего отчёта они исправили конфигурацию и добавили дополнительные проверки для всех входящих JWT-токенов. Этот случай — отличный пример того, как даже небольшая ошибка в настройке может привести к полной компрометации системы безопасности.
- Key ID (kid) Manipulation
Суть атаки: манипуляция параметром kid в header может привести к использованию скомпрометированного или слабого ключа для проверки подписи.
Защита:
- Валидируйте параметр kid перед использованием.
- Используйте белый список допустимых kid.
- Не позволяйте kid указывать на произвольные файлы или URL.
Помните: безопасность JWT зависит от правильной реализации на всех уровнях — от генерации до проверки токена. Используйте проверенные библиотеки, но всегда понимайте, как они работают и правильно их настраивайте. 🛡️
Практики защиты payload и реализация JWT в продакшене
Чтобы обеспечить максимальную защиту JWT в продакшн-окружении, необходимо комплексно подходить к проектированию системы аутентификации и авторизации. Рассмотрим ключевые практики, которые следует внедрить:
- Минимизация данных в payload
Оптимальный JWT содержит только необходимый минимум информации. Используйте токен как идентификатор сессии, а не контейнер для данных:
// Плохо
{
"sub": "1234567890",
"name": "Иван Петров",
"email": "ivan@example.com",
"phone": "+7-900-123-45-67",
"permissions": ["admin", "editor", "viewer"],
"groups": ["finance", "management", "reports"],
"office_location": "Moscow",
"department": "IT",
"manager_id": "9876543210",
"hire_date": "2018-06-01"
}
// Хорошо
{
"sub": "1234567890",
"iat": 1622505600,
"exp": 1622509200,
"jti": "random-unique-string"
}
Дополнительные данные лучше запрашивать отдельно после аутентификации пользователя.
- Управление жизненным циклом токенов
Стратегия управления токенами должна включать:
- Короткое время жизни (TTL) для access токенов (15-60 минут)
- Использование refresh токенов для обновления access токенов
- Механизм отзыва токенов (например, через Redis или другое хранилище)
- Ротацию ключей подписи по расписанию
Пример конфигурации системы токенов:
// Конфигурация токенов
{
"accessToken": {
"ttl": 900, // 15 минут в секундах
"algorithm": "RS256",
"issuer": "auth.example.com"
},
"refreshToken": {
"ttl": 2592000, // 30 дней в секундах
"rotationPolicy": "oneTimeUse", // Одноразовый refresh токен
"familyPolicy": true // Отзыв всех токенов при компрометации
},
"keyRotation": {
"schedule": "0 0 1 * *", // Ежемесячно (cron-формат)
"overlapPeriod": 86400 // 1 день для плавного перехода
}
}
- Безопасное хранение и передача токенов
- На сервере:
- Храните секретные ключи в защищенных хранилищах (HashiCorp Vault, AWS KMS, Azure Key Vault)
- Используйте переменные среды или конфигурационные файлы с ограниченными разрешениями
- Никогда не хардкодьте секреты в исходном коде
- На клиенте:
- Храните access токены в памяти (если возможно)
- Используйте HttpOnly, Secure, SameSite=Strict cookies
- Избегайте localStorage и sessionStorage для хранения токенов
- Шифрование чувствительных данных
Если необходимо передавать чувствительную информацию в токене, используйте JWT Encryption (JWE) вместо обычного JWT. JWE обеспечивает шифрование payload, а не только его подпись.
Пример структуры JWE:
{protected header}.{encrypted key}.{initialization vector}.{ciphertext}.{authentication tag}
При использовании JWE выбирайте современные алгоритмы шифрования, такие как:
- Шифрование ключа: RSA-OAEP, ECDH-ES
- Шифрование содержимого: A256GCM
- Мониторинг и аудит
Настройте мониторинг использования JWT в вашей системе:
- Логируйте все необычные события (неверные подписи, истекшие токены, атаки на алгоритм)
- Внедрите систему оповещений о подозрительной активности
- Проводите регулярный аудит настроек безопасности
- Используйте сканеры уязвимостей, специфичные для JWT
Пример простой интеграции JWT с Express.js и продвинутой обработкой ошибок:
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
// Middleware для проверки JWT
const authenticateJWT = (req, res, next) => {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({ error: 'Authorization header missing' });
}
const token = authHeader.split(' ')[1];
try {
// Явно указываем ожидаемый алгоритм
const user = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['RS256'], // Только RS256, защита от атаки подмены алгоритма
issuer: 'auth.example.com', // Проверка издателя
audience: 'api.example.com', // Проверка аудитории
});
req.user = user;
next();
} catch (error) {
if (error.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
if (error.name === 'JsonWebTokenError') {
// Логируем попытки использования неверных токенов
console.warn(`JWT validation failed: ${error.message}, token: ${token.substring(0, 10)}...`);
return res.status(403).json({ error: 'Invalid token' });
}
if (error.name === 'NotBeforeError') {
return res.status(401).json({ error: 'Token not yet valid' });
}
return res.status(500).json({ error: 'Internal server error' });
}
};
// Защищенный маршрут
app.get('/api/protected', authenticateJWT, (req, res) => {
res.json({ message: 'Protected data', user: req.user.sub });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Помните, что безопасность JWT — это не просто выбор правильного алгоритма. Это комплексный подход, включающий архитектурные решения, процедуры и постоянный мониторинг. 🚀
JWT (JSON Web Token) — мощный, но требующий осторожности инструмент. Правильно настроенный, он обеспечивает надёжный механизм аутентификации и авторизации. Однако каждое решение имеет последствия: используйте подходящие алгоритмы, защищайте ключи, минимизируйте данные в payload, внедряйте систему отзыва токенов. Безопасность — это непрерывный процесс, а не конечное состояние. Практикуйте принцип Zero Trust даже с проверенными технологиями, и помните: иногда самая надёжная система — та, что проще всего понять и поддерживать.
Элина Баранова
разработчик Android