Кэширование JWKS и настройка времени жизни кэша

JWKS-кэширование в jose строится вокруг механизма createRemoteJWKSet, который решает задачу получения и переиспользования JSON Web Key Set без постоянных сетевых запросов к провайдеру идентификации. При корректной настройке кэша достигается баланс между актуальностью ключей и производительностью проверки JWT.

При верификации JWT с асимметричным алгоритмом (например, RS256, ES256) библиотеке требуется публичный ключ. Вместо хранения ключей локально используется JWKS endpoint, который возвращает набор ключей в формате JSON.

jose при использовании:

import { createRemoteJWKSet } from 'jose'

const JWKS = createRemoteJWKSet(new URL('https://example.com/.well-known/jwks.json'))

создаёт функцию-резолвер ключей, которая:

  • выполняет HTTP-запрос к JWKS endpoint
  • извлекает набор ключей
  • выбирает нужный ключ по kid
  • кэширует результат для повторного использования

Ключевая идея заключается в том, что повторные проверки токенов не должны каждый раз инициировать сетевой запрос.


Внутренний кэш и его поведение

Внутренний кэш createRemoteJWKSet работает как in-memory структура, привязанная к экземпляру функции. Он хранит:

  • последний полученный JWKS
  • метаданные ответа
  • время последнего обновления
  • ключи, индексированные по kid

При последующих вызовах происходит:

  1. Поиск ключа в кэше
  2. Если ключ найден и не истёк TTL — используется локально
  3. Если ключ отсутствует или устарел — выполняется обновление JWKS

TTL (time-to-live) кэша и его настройка

Основной параметр управления временем жизни кэша — cacheMaxAge. Он определяет, как долго JWKS считается валидным без повторного запроса.

const JWKS = createRemoteJWKSet(
  new URL('https://example.com/.well-known/jwks.json'),
  {
    cacheMaxAge: 60 * 60 * 1000 // 1 час
  }
)

Поведение cacheMaxAge

  • Значение указывается в миллисекундах
  • После истечения времени кэш принудительно обновляется
  • До истечения TTL запросы полностью обслуживаются из памяти

Баланс между безопасностью и производительностью

Выбор TTL напрямую влияет на поведение системы:

Короткий TTL (например, 5–10 минут)

  • Быстрое распространение ротации ключей
  • Повышенная нагрузка на JWKS endpoint
  • Увеличение сетевых задержек

Длинный TTL (например, 1–24 часа)

  • Минимальное количество сетевых запросов
  • Высокая производительность проверки токенов
  • Риск использования устаревших ключей при ротации

Практически TTL выбирается исходя из политики ротации ключей у провайдера идентификации.


Дополнительные механизмы кэширования

Помимо cacheMaxAge, в jose присутствуют дополнительные механизмы защиты от перегрузки и некорректных обновлений.

cooldownDuration

Ограничивает частоту повторных запросов при ошибках или отсутствующих ключах.

const JWKS = createRemoteJWKSet(url, {
  cooldownDuration: 30 * 1000
})

Поведение:

  • После неудачного запроса библиотека не пытается сразу повторить запрос
  • В течение cooldown-периода используется последний известный набор ключей

timeoutDuration

Определяет максимальное время ожидания ответа JWKS endpoint.

const JWKS = createRemoteJWKSet(url, {
  timeoutDuration: 5000
})

Это важно для предотвращения блокировки потока проверки JWT при медленных или недоступных сервисах.


maxCachedAge vs cacheMaxAge

В разных версиях и конфигурациях можно встретить:

  • cacheMaxAge — основное управление TTL
  • внутренние ограничения HTTP-кеша (ETag / Cache-Control)
  • поведение может дополняться HTTP-заголовками ответа JWKS

Если сервер возвращает:

Cache-Control: max-age=3600

то библиотека может учитывать это значение как ориентир, если явно не переопределено конфигурацией.


Ротация ключей и влияние на кэш

При ротации ключей важно понимать, что JWKS содержит набор ключей одновременно. Это означает:

  • новый ключ добавляется в JWKS заранее
  • старый ключ остаётся доступным до окончания переходного периода

Кэш в jose не ломает этот процесс, поскольку:

  • хранит весь набор ключей, а не один
  • ищет ключ по kid, а не по позиции

Проблема возникает только при слишком длинном TTL, когда новый ключ ещё не загружен, а старый уже отозван.


Стратегии настройки TTL в реальных системах

OAuth / OpenID Connect провайдеры

Обычно используют TTL в диапазоне:

  • 15–60 минут
  • иногда до 24 часов при стабильной инфраструктуре

Высоконагруженные API

  • 5–15 минут
  • при наличии кластера JWKS endpoint с низкой задержкой

Финансовые и критические системы

  • короткий TTL (1–5 минут)
  • строгий контроль ротации ключей

Оптимизация поведения кэша

Для систем с высокой нагрузкой важно учитывать:

  • количество проверок JWT в секунду
  • стабильность JWKS endpoint
  • частоту ротации ключей

Практический подход:

  • TTL устанавливается чуть больше интервала ротации ключей
  • cooldown защищает от лавины запросов при сбоях
  • timeout предотвращает блокировки event loop

Типичные ошибки при настройке

Слишком агрессивное кэширование

Если TTL превышает жизненный цикл ключей:

  • возникает ошибка invalid signature
  • новые ключи не успевают попасть в кэш

Полное отключение кэша через частые пересоздания JWKS клиента

Если createRemoteJWKSet вызывается на каждый запрос:

  • кэш фактически отсутствует
  • каждый JWT verification вызывает HTTP-запрос

Игнорирование cooldown

При нестабильном JWKS endpoint возможен:

  • flood запросов при ошибках
  • деградация производительности сервиса

Поведение при недоступности JWKS endpoint

Если endpoint временно недоступен:

  • используется кэшированная версия ключей
  • при отсутствии кэша происходит ошибка верификации
  • cooldown предотвращает повторные запросы в коротком интервале

Такой подход делает систему устойчивой к кратковременным сбоям внешнего провайдера.


Итоговая модель работы кэша JWKS в jose

Система кэширования строится на трёх слоях:

  • локальный in-memory cache JWKS
  • TTL через cacheMaxAge
  • защитные механизмы cooldownDuration и timeoutDuration

Эта комбинация позволяет добиться предсказуемой проверки JWT при минимальном количестве сетевых запросов и контролируемой актуальности криптографических ключей.