Перейти к содержимому
ecdsa.com
Java · Kotlin

Nimbus JOSE + JWT

Nimbus JOSE + JWT от Connect2id — библиотека JOSE внутри Spring Security и многих серверов OAuth на Java: в процессоре селектор ключей служит списком разрешённых алгоритмов, неподписанные токены отклоняются, а допуск на расхождение часов по умолчанию равен 60 секундам.
Лицензия
Apache-2.0
Проверенная версия
10.10 ·
Исходный код
bitbucket.org/connect2id/nimbus-jose-jwt
Документация
connect2id.com/products/nimbus-jose-jwt

Совместимость вкратце

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

Nimbus поддерживает JWS, JWE, JWK и JWT на двух уровнях: низкоуровневые объекты (SignedJWT, ECDSASigner, MACVerifier) и DefaultJWTProcessor, который объединяет выбор ключа, проверку подписи и проверку утверждений. На нём построен сервер ресурсов Spring Security, поэтому его настройки по умолчанию действуют в большинстве API на Spring.

В процессоре списком разрешённых алгоритмов служит JWSVerificationKeySelector: селектор для ES256 отклонил токен RS256 и токен HS256, подписанный текстом PEM ключа EC, с ошибкой Another algorithm expected, or no matching key(s) found, а без селектора процессор отклоняет любые токены. Неподписанные токены отклоняются (Unsecured (plain) JWTs are rejected, extend class to handle). У низкоуровневого API такой защиты нет: MACVerifier, созданный из текста PEM, принял этот поддельный токен HS256, поэтому код, который выбирает объект проверки по заголовку токена, воспроизводит классическую ошибку подмены алгоритма. Используйте процессор, как это делает пример.

DefaultJWTClaimsVerifier по умолчанию допускает расхождение часов в 60 секунд, а exp необязателен, если не указать его среди обязательных утверждений; пример требует sub и exp и фиксирует издателя. ES256 не принимает ключ P-384 (The ES256 algorithm is not allowed or supported by the JWS signer), а подпись в кодировке DER просто не проходит проверку; проверьте длину подписи в отладчике JWT.

В версии 10.10 полный цикл прошли семейства HS, RS, PS и ES, а также EdDSA и более новое имя Ed25519. Для Ed25519 нужна библиотека Google Tink в classpath: без неё подпись завершилась ошибкой NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair. Для ES256K нужен провайдер с поддержкой secp256k1, например Bouncy Castle, поскольку в JDK такого провайдера нет.

Что публикует поставщик удостоверений, покажет проверка JWKS, а зашифрованные токены разберёт инспектор JWE.

Факты и проверки, на которых они основаны

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/nimbus-jose-jwt/run.sh запустила Nimbus JOSE + JWT 10.10 в образе контейнера maven:3.9.16-eclipse-temurin-25; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

    Подпись и проверка на JDK работают для HS, RS, PS и ES на 256/384/512, EdDSA и Ed25519; ES256K и Ed448 с провайдерами JDK полный цикл не прошли.

    Что записала проверка
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA, Ed25519
    evidence jws-algorithms: ES256K: not round-tripped with the JDK providers
    evidence jws-algorithms: Ed448: not round-tripped with the JDK providers
  • Кодировка подписи ES256

    raw

    Подписи ES256 — 64 байта в формате raw r‖s.

    Что записала проверка
    fact jws-ecdsa-encoding = raw
  • Подпись в DER внутри JWS

    rejected

    Токен с подписью ES256 в кодировке DER не прошёл проверку.

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: not verified
  • Список разрешённых алгоритмов

    required

    Для DefaultJWTProcessor нужен селектор ключей: без него возникает ошибка No JWS key selector is configured, а селектор для ES256 отклонил токен RS256.

    Что записала проверка
    fact jwt-algorithm-allowlist = required
    evidence jwt-algorithm-allowlist: DefaultJWTProcessor with a JWSVerificationKeySelector for ES256, RS256 token: BadJOSEException: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
    evidence jwt-algorithm-allowlist: DefaultJWTProcessor without a key selector: BadJOSEException: Signed JWT rejected: No JWS key selector is configured
  • Неподписанные токены (alg none)

    rejected

    Процессор отклоняет неподписанные токены с ошибкой Unsecured (plain) JWTs are rejected, extend class to handle.

    Что записала проверка
    fact jwt-alg-none = rejected
    evidence jwt-alg-none: DefaultJWTProcessor: BadJOSEException: Unsecured (plain) JWTs are rejected, extend class to handle
    evidence jwt-alg-none: SignedJWT.parse: ParseException: Invalid JWS header: Not a JWS header
  • Открытый ключ в роли секрета HMAC

    rejected

    Токен HS256, подписанный текстом PEM в качестве ключа, был отклонён процессором с селектором для ES256, но прошёл проверку в MACVerifier, вручную созданном из того же текста PEM.

    Что записала проверка
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: DefaultJWTProcessor with the ES256 key selector: BadJOSEException: Signed JWT rejected: Another algorithm expected, or no matching key(s) found
    evidence jwt-hmac-public-key: a MACVerifier built from the PEM text by hand: verified
  • Сверка кривой с alg

    enforced

    При подписи ES256 ключом P-384 возникает ошибка The ES256 algorithm is not allowed or supported by the JWS signer: Supported algorithms: [ES384].

    Что записала проверка
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: JOSEException: The ES256 algorithm is not allowed or supported by the JWS signer: Supported algorithms: [ES384]
  • Допустимое расхождение часов по умолчанию

    60s

    DefaultJWTClaimsVerifier допускает расхождение часов в 60 секунд (DEFAULT_MAX_CLOCK_SKEW_SECONDS).

    Что записала проверка
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: DefaultJWTClaimsVerifier.DEFAULT_MAX_CLOCK_SKEW_SECONDS = 60
  • Обязательное утверждение о сроке действия

    no

    Токен без exp прошёл проверку утверждений с настройками по умолчанию; когда exp указали как обязательное утверждение, проверка завершилась ошибкой JWT missing required claims: [exp].

    Что записала проверка
    fact jwt-exp-required = no
    evidence jwt-exp-required: with DefaultJWTClaimsVerifier requiring exp: BadJWTException: JWT missing required claims: [exp]
  • Ed25519 без Google Tink

    fails

    Без Google Tink в classpath подпись через Ed25519Signer завершилась ошибкой NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair.

    Что записала проверка
    fact ed25519-without-tink = fails
    evidence ed25519-without-tink: NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair

Использование

Подпишите токен ES256 с идентификатором ключа и обработайте его с селектором ключей, который допускает только ES256, и проверкой утверждений, которая требует sub, exp и издателя.
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.ECDSASigner;
import com.nimbusds.jose.jwk.*;
import com.nimbusds.jose.jwk.gen.ECKeyGenerator;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.proc.*;
import com.nimbusds.jwt.*;
import com.nimbusds.jwt.proc.*;
import java.util.*;

public class Usage {
    public static void main(String[] args) throws Exception {
        ECKey key = new ECKeyGenerator(Curve.P_256).keyID("2026-10").generate();
        JWTClaimsSet claims = new JWTClaimsSet.Builder().subject("user-42").issuer("https://auth.example.com")
            .expirationTime(new Date(System.currentTimeMillis() + 600_000)).build();
        SignedJWT jwt = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.ES256).keyID(key.getKeyID()).build(), claims);
        jwt.sign(new ECDSASigner(key));

        // The key selector is the algorithm allow-list; the claims verifier requires iss and exp.
        DefaultJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
        processor.setJWSKeySelector(new JWSVerificationKeySelector<>(JWSAlgorithm.ES256, new ImmutableJWKSet<>(new JWKSet(key.toPublicJWK()))));
        processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(
            new JWTClaimsSet.Builder().issuer("https://auth.example.com").build(), new HashSet<>(List.of("sub", "exp"))));
        System.out.println("sub: " + processor.process(jwt.serialize(), null).getSubject());
    }
}

Тестовый стенд для фрагментов кода запускает каждый пример без изменений в образе контейнера maven:3.9.16-eclipse-temurin-25, и запуск завершается ошибкой, если вывод не совпадает.

Подготовка
mvn -q -B dependency:copy -Dartifact=com.nimbusds:nimbus-jose-jwt:10.10 -DoutputDirectory=lib >/dev/null
Команда
java -cp lib/nimbus-jose-jwt-10.10.jar Usage.java
Вывод должен совпадать (регулярное выражение)
sub: user-42

Библиотеки