Какие существуют способы передачи публичного ключа для верификации JWT?

Ответ

Основной и рекомендуемый способ — использование JWKS (JSON Web Key Set). Это стандартный механизм, который позволяет клиентам динамически получать публичные ключи для проверки подписи JWT.

1. JWKS (JSON Web Key Set) — Стандартный подход

Это публичный эндпоинт (например, /.well-known/jwks.json), который возвращает JSON-объект с набором ключей. Такой подход позволяет серверу аутентификации ротировать ключи без необходимости вносить изменения в клиентские приложения.

Пример ответа JWKS:

{
  "keys": [
    {
      "kty": "RSA",
      "use": "sig",
      "kid": "unique-key-id-1",
      "alg": "RS256",
      "n": "...base64url-encoded-modulus...",
      "e": "AQAB"
    }
  ]
}
  • kty: Тип ключа (например, RSA).
  • use: Назначение ключа (sig — для подписи).
  • kid: Уникальный идентификатор ключа. JWT в своем заголовке содержит kid, чтобы клиент знал, какой именно ключ из набора использовать для верификации.

2. Другие способы (менее гибкие)

  • Встраивание в конфигурацию клиента: Публичный ключ жестко прописывается в конфигурации клиентского приложения. Главный минус — при смене ключа на сервере требуется обновление всех клиентов.
  • Передача в заголовке JWT (jwk): Публичный ключ встраивается непосредственно в заголовок токена. Используется редко, так как значительно увеличивает размер токена.

Пример получения ключа из JWKS на Python:

import jwt
import requests

# URL эндпоинта с ключами
jwks_url = "https://auth.example.com/.well-known/jwks.json"

# Получаем JWT, который нужно проверить
token = "eyJhbGciOiJSUzI1NiIsImtpZCI6InVuaXF1ZS1rZXktaWQtMSJ9..."

# 1. Получаем kid из заголовка токена
header = jwt.get_unverified_header(token)
kid = header['kid']

# 2. Загружаем ключи с JWKS-эндпоинта
jwks_client = jwt.PyJWKClient(jwks_url)
signing_key = jwks_client.get_signing_key_from_jwt(token)

# 3. Верифицируем токен с помощью найденного ключа
data = jwt.decode(
    token, 
    signing_key.key, 
    algorithms=["RS256"], 
    options={"verify_aud": False}
)
print(data)

Ответ 18+ 🔞

Давай разберём, как серверы обычно отдают свои публичные ключи для проверки JWT, чтобы клиенты могли понять, не подделан ли токен. А то ведь, блядь, любой распиздяй может нарисовать себе токен в блокноте и кричать, что он — король интернета.

1. JWKS (JSON Web Key Set) — Правильный, ебать, подход

Это самый ахуенный и стандартный способ. Сервер аутентификации выставляет на всеобщее обозрение специальный эндпоинт, типа /.well-known/jwks.json. Туда любой желающий (клиент) может прийти и забрать свежие публичные ключи. Главная фишка — сервер может менять свои ключи (ротировать), а клиентам даже не нужно будет перезапускаться, они просто в следующий раз возьмут новый ключ из этого набора. Умно, сука!

Как выглядит этот ответ:

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

Смотри, что тут есть:

  • kty: Тип ключа. Обычно RSA, но бывают и другие, епта.
  • use: Зачем ключ. sig — значит для подписи, а не для шифрования.
  • kid: Уникальный ид ключа. Это, блядь, самое важное! Когда сервер подписывает JWT, он в заголовок токена пишет этот самый kid. Клиент смотрит в токен, видит kid, потом лезет в JWKS, находит ключ с таким же kid — и вуаля, он знает, каким ключом проверять подпись. Красота, в рот меня чих-пых!

2. Другие способы (которые обычно — говно)

  • Зашить ключ в клиент намертво. Берешь публичный ключ и пишешь его прямо в конфиг своего приложения. Минус, блядь, очевиден: если на сервере ключ поменяют, то все твои клиенты разом накроются медным тазом, пока ты не обновишь конфиг у каждого. Пиздец, а не подход.
  • Засунуть ключ прямо в заголовок JWT. Да, можно и так, поле jwk называется. Но это увеличивает размер токена до овердохуища, поэтому так почти не делают.

Ну и как это всё на Python выглядит, пример:

import jwt
import requests

# Адрес, где лежат ключи
jwks_url = "https://auth.example.com/.well-known/jwks.json"

# Токен, который нам прислал какой-то чувак и который надо проверить
token = "eyJhbGciOiJSUzI1NiIsImtpZCI6InVuaXF1ZS1rZXktaWQtMSJ9..."

# 1. Смотрим, не обманывают ли нас. Вытаскиваем заголовок токена без проверки.
header = jwt.get_unverified_header(token)
kid = header['kid']  # Выцепляем идентификатор ключа (kid)

# 2. Идём на сервер и качаем актуальные ключи
jwks_client = jwt.PyJWKClient(jwks_url)
signing_key = jwks_client.get_signing_key_from_jwt(token)

# 3. А теперь, сука, проверяем токен этим ключом!
data = jwt.decode(
    token,
    signing_key.key,
    algorithms=["RS256"],
    options={"verify_aud": False}
)
print(data)  # Если не вылетела ошибка — токен валидный, можно работать с данными внутри.

Вот так, блядь, и живём. Сервер крутит ключами, а клиенты их сами подтягивают. Никакой магии, один стандарт и немного кода.