JWT на собеседовании системного аналитика
Карьерник — Duolingo для аналитиков: 10 минут в день тренируй SQL, Python, A/B, статистику, метрики и ещё 3 темы собеса. 1500+ вопросов в Telegram-боте. Бесплатно.
Содержание:
Зачем JWT спрашивают
JWT — стандартный формат токенов в OAuth/OIDC. Системный аналитик при описании интеграции должен ответить: что внутри токена, как он валидируется, что делать при компрометации.
Главная боль без понимания JWT — кандидат говорит «токен подписан, значит его нельзя подделать», но не различает HS256 (общий секрет) и RS256 (асимметричная подпись). На банковском собесе это проваливает кандидата сразу: банковские микросервисы используют RS256, потому что секрет нельзя раздавать на 50+ сервисов.
JWT (RFC 7519) — это про «передать данные о пользователе/клиенте между сервисами в подписанном виде». Не очередь, не шифрование, а подписанный конверт.
Структура токена
JWT — три части, разделённые точкой, base64url-закодированные:
header.payload.signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJleHAiOjE3MzAwMDAwMDB9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cHeader — алгоритм подписи и тип токена:
{
"alg": "HS256",
"typ": "JWT",
"kid": "key-id-2026"
}Payload — claims (утверждения о субъекте):
{
"sub": "user-123",
"iss": "https://auth.example.com",
"aud": "orders-api",
"exp": 1730000000,
"iat": 1729996400,
"scope": "read:orders write:orders"
}Signature — HMAC или RSA подпись от base64url(header) + "." + base64url(payload).
Важно: payload не зашифрован, только подписан. Любой может декодировать base64 и прочитать содержимое. Не класть в JWT пароли, токены чужих систем, чувствительные данные.
Стандартные claims
Зарегистрированные в RFC 7519:
iss(issuer) — кто выдал. Используется для выбора JWKS для валидации.sub(subject) — о ком токен (обычно user_id).aud(audience) — для какого API. API должен проверять, чтоaudсоответствует ему.exp(expiration) — Unix timestamp истечения.nbf(not before) — токен не валиден раньше этого времени.iat(issued at) — когда выдан.jti(JWT ID) — уникальный ID токена для отзыва.
Custom claims — любые свои: scope, role, tenant_id, user_email. Не использовать имена, конфликтующие с registered claims.
OIDC добавляет: name, email, email_verified, preferred_username, picture, locale.
HS256 vs RS256
HS256 (HMAC-SHA256) — симметричная подпись общим секретом. Тот, кто проверяет, тот же кто и подписывает.
secret = "super-secret-key"
signature = HMAC-SHA256(header.payload, secret)Плюс: быстро, просто. Минус: секрет должны знать все валидирующие сервисы. На 30 микросервисах — секрет в 30 местах. Утечка одного — компрометация всех.
Уместно: монолит, один-два сервиса под одним секретом.
RS256 (RSA-SHA256) — асимметричная. Authorization server подписывает приватным ключом, валидирующие сервисы проверяют публичным.
signature = RSA-Sign(header.payload, private_key)
verify(token, public_key) = true/falseПубличный ключ публикуется через JWKS:
GET /.well-known/jwks.json
{
"keys": [
{"kid": "key-id-2026", "kty": "RSA", "n": "...", "e": "AQAB", "alg": "RS256"}
]
}Сервисы кешируют JWKS, выбирают ключ по kid из header.
Плюс: приватный ключ — только у authorization server. 30 сервисов знают только публичный, утечка ничего не даёт. Минус: дороже по CPU (но почти всегда не bottleneck).
Уместно: микросервисная архитектура, multi-tenant, продакшен в банке/маркетплейсе.
ES256 (ECDSA) — современная альтернатива RSA: меньше ключи, та же безопасность.
Валидация на стороне сервиса
Минимальный чек-лист валидации:
- Подпись — пересчитать и сравнить. Если не совпала — отказать.
algв header неnone(известная атакаalg=noneобходит проверку подписи в плохих библиотеках).exp— токен не истёк. С учётом небольшого clock skew (1–5 минут).nbf— текущее время ≥nbf.iss— токен от ожидаемого issuer.aud— этот сервис в audience.scope/role — есть ли право на запрашиваемое действие.
Псевдокод:
def validate_jwt(token, jwks, expected_iss, expected_aud):
header = decode_header(token)
key = jwks_lookup(jwks, header["kid"])
if not verify_signature(token, key, header["alg"]):
raise Unauthorized
payload = decode_payload(token)
now = time.time()
if payload["exp"] < now - CLOCK_SKEW: raise Unauthorized
if payload.get("nbf", 0) > now + CLOCK_SKEW: raise Unauthorized
if payload["iss"] != expected_iss: raise Unauthorized
if expected_aud not in payload["aud"]: raise Unauthorized
return payloadИспользовать проверенные библиотеки (jose, pyjwt, nimbus-jose-jwt), не писать своё.
Отзыв токенов
Главная боль JWT — токен живёт до exp, мгновенный отзыв из коробки невозможен.
Стратегии:
1. Короткий TTL access + refresh.
Access живёт 5–15 минут. После logout убиваем refresh token в БД authorization server. Через 15 минут access перестаёт быть валидным сам по себе.
Минус: до 15 минут окно компрометации.
2. Чёрный список (deny list).
При logout / revoke — добавляем jti в Redis с TTL = оставшийся срок токена. Все сервисы при валидации проверяют не только подпись и exp, но и отсутствие в deny list.
Минус: лишняя сетевая проверка на каждый запрос. Плюс: точечный отзыв.
3. Reference token / opaque + introspection.
Token не JWT, а ссылка. Сервис делает POST /introspect к authorization server. Состояние токена — на сервере, отзыв мгновенный.
Минус: каждый запрос — сетевой вызов. Плюс: гибкость и мгновенный revocation.
На собесе классический вопрос: «Как отзывать JWT?» Ответ: «JWT нельзя отозвать без хранения состояния. Варианты — короткий TTL + revoke refresh, чёрный список по jti, или вообще не использовать JWT для критичных операций (брать opaque + introspect)».
Частые ошибки
Хранить чувствительные данные в payload. Payload не зашифрован. Никаких паролей, токенов сторонних API, банковских реквизитов.
alg=none. В некоторых старых библиотеках сервис принимает токен с alg=none без проверки. Атака: подменить header, удалить подпись. Использовать библиотеки, которые явно не разрешают none.
Доверять iss без валидации. Подделка iss без проверки JWKS — возможна. Сервис должен принимать только список доверенных issuers.
Игнорировать aud. Токен, выписанный для API A, можно подсунуть API B. Всегда проверять aud.
HS256 в распределённой системе. Секрет в 50 местах. Использовать RS256/ES256 с JWKS.
Долгий access (часы/дни). Невозможность мгновенного отзыва превращается в окно компрометации в часы.
Сохранять JWT в localStorage. XSS-уязвимое место. Httponly cookies или secure storage.
Связанные темы
- OAuth 2.1 на собесе SA
- REST API на собесе SA
- Подготовка к собесу системного аналитика
- HTTP методы и коды на собесе SA
- Сoбеседование системного аналитика
FAQ
JWT и session token — это одно и то же?
Нет. Session token — opaque строка, состояние в БД сервера. JWT — самопроверяемый токен с claims. JWT не требует БД для валидации, session token требует.
Можно ли расшифровать JWT?
Стандартный JWT (JWS) не зашифрован, только подписан. JWE — encrypted JWT, шифруется содержимое. Используется реже, обычно достаточно подписи + HTTPS.
Какой TTL ставить access token?
5–60 минут типично. Короче — больше нагрузка на refresh. Длиннее — окно компрометации. Для критичных операций (банк, медицина) — 5–15 минут.
Как валидировать JWT в микросервисе без сетевых вызовов?
Кешировать JWKS на стороне сервиса (с TTL и фоновым обновлением). При запросе — выбираем ключ по kid из header, валидируем локально. Сетевой вызов только при ротации ключей.
Что делать при ротации ключей?
JWKS возвращает несколько ключей. Authorization server подписывает новые токены новым kid, старые продолжают валидироваться по старому ключу. Старый ключ удаляется из JWKS после истечения всех токенов, им подписанных.
Это официальная информация?
Нет. Статья основана на RFC 7519 (JWT), RFC 7515 (JWS), RFC 7517 (JWK) и опыте кандидатов. Конкретные требования зависят от компании.
Тренируйте системный анализ — откройте тренажёр с 1500+ вопросами для собесов.