JSON Web Key Set (JWKS): структура

JSON Web Key Set представляет собой контейнер, в котором публикуется набор криптографических ключей в формате JSON Web Key (JWK). Такой формат используется для распространения публичных ключей, применяемых при проверке подписи JSON Web Token (JWT). В контексте Jsrsasign работа с JWKS позволяет автоматически извлекать и использовать нужный ключ для проверки подписи токена без ручного управления сертификатами или PEM-файлами.

JWKS всегда представляет собой JSON-объект с фиксированной структурой, где основным элементом является массив ключей.

Минимально корректный JWKS выглядит следующим образом:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "example-key-id",
      "use": "sig",
      "alg": "RS256",
      "n": "modulus-base64url",
      "e": "AQAB"
    }
  ]
}

Ключевым элементом является поле keys, которое содержит массив объектов JWK. Каждый объект описывает один криптографический ключ и его метаданные.

Поле keys

keys — обязательное поле JWKS. Оно всегда является массивом. В него могут входить ключи разных типов и назначения одновременно.

Пример расширенного набора:

{
  "keys": [
    { "kty": "RSA", "kid": "key-1", "use": "sig", "alg": "RS256", "n": "...", "e": "AQAB" },
    { "kty": "EC", "kid": "key-2", "use": "sig", "crv": "P-256", "x": "...", "y": "..." }
  ]
}

Такая структура используется для поддержки ротации ключей и одновременного существования нескольких алгоритмов подписи.

Структура JWK внутри JWKS

Каждый элемент массива keys — это JSON Web Key. Он содержит обязательные и опциональные поля, зависящие от типа криптографии.

Основные общие поля

Независимо от алгоритма, часто встречаются следующие атрибуты:

  • kty — тип ключа (Key Type)
  • kid — идентификатор ключа
  • use — назначение ключа (например, sig для подписи)
  • alg — алгоритм, для которого предназначен ключ

Поле kty

kty определяет криптографический тип ключа:

  • RSA — RSA-ключи
  • EC — эллиптические кривые
  • oct — симметрические ключи (секреты)

От этого значения зависит набор обязательных параметров.

RSA-ключ в JWKS

RSA-ключ является наиболее распространённым вариантом для JWT подписи.

{
  "kty": "RSA",
  "kid": "rsa-1",
  "use": "sig",
  "alg": "RS256",
  "n": "base64url-modulus",
  "e": "AQAB"
}

Поля RSA

  • n — модуль RSA (base64url)
  • e — публичная экспонента (обычно AQAB, что соответствует 65537)

Именно эти параметры используются Jsrsasign для восстановления публичного ключа.

EC-ключ в JWKS

EC (Elliptic Curve) ключи используются в алгоритмах типа ES256, ES384 и ES512.

{
  "kty": "EC",
  "kid": "ec-1",
  "use": "sig",
  "crv": "P-256",
  "x": "base64url-x",
  "y": "base64url-y",
  "alg": "ES256"
}

Поля EC

  • crv — кривая (например, P-256, P-384, P-521)
  • x, y — координаты точки на эллиптической кривой

Эти значения представляют публичную точку ключа.

Симметричный ключ oct

Тип oct используется для HMAC-подписей.

{
  "kty": "oct",
  "kid": "hmac-1",
  "k": "base64url-secret",
  "alg": "HS256"
}

Поле k

  • k — секретный ключ, закодированный в base64url

Такой ключ применяется только на стороне сервера, так как требует секретного значения.

Поле kid и механизм выбора ключа

kid (Key ID) — критически важный элемент JWKS. Он позволяет определить, какой ключ использовать для проверки конкретного JWT.

В заголовке JWT обычно присутствует:

{
  "alg": "RS256",
  "kid": "rsa-1"
}

При проверке библиотека:

  1. извлекает kid из токена
  2. ищет соответствующий ключ в JWKS
  3. использует найденный ключ для проверки подписи

Работа с JWKS в Jsrsasign

В Jsrsasign работа с JWKS осуществляется через KEYUTIL.

Загрузка JWKS

JWKS можно получить как строку JSON и разобрать:

const jwks = JSON.parse(jwksString);

Далее используется преобразование ключей:

const key = KEYUTIL.getKey(jwks.keys[0]);

Однако более типичный сценарий — поиск ключа по kid.

Поиск ключа по kid

function getKeyFromJWKS(jwks, kid) {
  const jwk = jwks.keys.find(k => k.kid === kid);
  if (!jwk) throw new Error("Key not found");
  return KEYUTIL.getKey(jwk);
}

Проверка JWT с использованием JWKS

const isValid = KJUR.jws.JWS.verifyJWT(token, key, {
  alg: ["RS256"]
});

В этом процессе ключ предварительно извлекается из JWKS.

JWKS URL и динамическая загрузка

В реальных системах JWKS часто размещается по URL:

https://example.com/.well-known/jwks.json

Пример загрузки:

async function loadJWKS(url) {
  const res = await fetch(url);
  return await res.json();
}

После загрузки ключи кешируются, чтобы избежать повторных запросов.

Ротация ключей

JWKS поддерживает одновременное существование нескольких ключей. Это позволяет безопасно менять ключи без прерывания работы системы.

Типичный сценарий:

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

Валидация структуры JWKS

Корректный JWKS должен соответствовать ряду требований:

  • наличие поля keys
  • каждый ключ содержит kty
  • RSA-ключи должны иметь n и e
  • EC-ключи должны иметь crv, x, y
  • ключи должны иметь уникальные kid

Нарушение структуры приводит к невозможности восстановления ключа в Jsrsasign.

Преобразование JWK в PEM через Jsrsasign

Jsrsasign позволяет преобразовать JWK в PEM:

const pem = KEYUTIL.getPEM(KEYUTIL.getKey(jwk));

Это удобно для интеграции с системами, ожидающими PEM-формат.

Ошибки при работе с JWKS

На практике часто встречаются следующие проблемы:

Отсутствие kid

Без kid невозможно выбрать нужный ключ при наличии нескольких.

Несоответствие алгоритма

Если JWT подписан RS256, а ключ помечен как ES256, проверка всегда будет провалена.

Повреждённые base64url поля

n, e, x, y должны быть корректно закодированы. Даже одна ошибка ломает восстановление ключа.

Устаревший кеш JWKS

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

Использование JWKS в архитектуре JWT

JWKS является частью стандартной схемы OpenID Connect и OAuth 2.0. Сервер авторизации публикует JWKS, а клиент:

  • загружает набор ключей
  • выбирает ключ по kid
  • проверяет подпись JWT
  • не хранит секреты локально

Такая модель обеспечивает централизованное управление ключами и упрощает масштабирование систем аутентификации.