JWT на собеседовании системного аналитика

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Карьерник — 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_adQssw5c

Header — алгоритм подписи и тип токена:

{
  "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: меньше ключи, та же безопасность.

Готовься к собесу аналитика как в Duolingo
10 минут в день — SQL, Python, A/B, метрики. 1700+ вопросов в Telegram
Открыть Карьерник в Telegram

Валидация на стороне сервиса

Минимальный чек-лист валидации:

  1. Подпись — пересчитать и сравнить. Если не совпала — отказать.
  2. alg в header не none (известная атака alg=none обходит проверку подписи в плохих библиотеках).
  3. exp — токен не истёк. С учётом небольшого clock skew (1–5 минут).
  4. nbf — текущее время ≥ nbf.
  5. iss — токен от ожидаемого issuer.
  6. aud — этот сервис в audience.
  7. 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.

Связанные темы

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+ вопросами для собесов.