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

JJWT (io.jsonwebtoken)

JJWT от Les Hazlewood — распространённая библиотека JWT для Java и Android: допустимый алгоритм определяется ключом проверки подписи, для неподписанных токенов нужен явный парсер unsecured(), а слабые ключи HMAC сразу отклоняются.
Лицензия
Apache-2.0
Проверенная версия
0.13.0 ·
Исходный код
github.com/jwtk/jjwt
Документация
github.com/jwtk/jjwt#readme

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

JOSE и JWT

Алгоритмы JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

JJWT начиная с версии 0.12 предоставляет построитель и парсер с fluent-интерфейсом — Jwts.builder()…signWith(key, alg) и Jwts.parser().verifyWith(key)…parseSignedClaims(token), а алгоритмы собраны в Jwts.SIG. Библиотека поставляется тремя артефактами: jjwt-api нужен при компиляции, а во время выполнения — jjwt-impl и модуль привязки к JSON, например jjwt-jackson. В версии 0.13.0 полный цикл подписи и проверки прошли HS, RS, PS и ES на 256/384/512 и EdDSA; ES256K и имён Ed25519 и Ed448 в Jwts.SIG нет.

Парсер выводит разрешённый алгоритм из ключа проверки подписи и прямо говорит об этом в сообщениях об ошибках. Токен RS256 при проверке открытым ключом EC и токен HS256, подписанный текстом PEM ключа EC, отклонены с исключением UnsupportedJwtException, в котором сказано, что этот ключ нельзя использовать для такого алгоритма. Защиту обеспечивает тип ключа, а не список: парсер, получивший Keys.hmacShaKeyFor(pemBytes), принял этот поддельный токен, поэтому никогда не создавайте ключи HMAC из данных, которые могут быть общедоступны.

parseSignedClaims отклоняет неподписанные токены с сообщением, которое ссылается на RFC 7518, § 3.6; прочитать их можно только через Jwts.parser().unsecured()…parseUnsecuredClaims. Слабые ключи HMAC до этого этапа не доходят: Keys.hmacShaKeyFor отклонил 16-байтовый ключ с исключением WeakKeyException, поскольку RFC 7518 требует, чтобы ключ был не короче хеша. ES256 не принимает ключ P-384 уже на этапе подписи.

Два значения по умолчанию требуют внимания. Допуска на расхождение часов нет (токен, срок действия которого истёк 3 секунды назад, не прошёл проверку), а exp необязателен, поэтому задайте clockSkewSeconds и проверяйте срок действия и издателя, как в примере. Подписи ES256 — это 64-байтовый формат raw r‖s; о подписи DER библиотека сообщает точно (Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes)), и это одна из немногих библиотек, которые прямо называют проблему с кодировкой. Разбирайте токены в отладчике JWT, а ключи HMAC проверяйте инструментом проверки секретов JWT.

Для масштабной работы с JWE и наборами JWK сравните JJWT с Nimbus JOSE + JWT.

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

Ничего из приведённого ниже не скопировано из документации. 20 октября 2026 г. проверка content/libraries/verify/jjwt/run.sh запустила JJWT (io.jsonwebtoken) 0.13.0 в образе контейнера maven:3.9.16-eclipse-temurin-25; содержимое страницы должно совпадать с выводом проверки, а у каждого факта приведены соответствующие строки вывода.

  • Алгоритмы JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

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

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

    raw

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

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

    rejected

    Подпись ES256 в кодировке DER не проходит проверку с ошибкой Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes).

    Что записала проверка
    fact jws-der-signature = rejected
    evidence jws-der-signature: SignatureException: Unable to verify Elliptic Curve signature using provided ECPublicKey: Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes) per [RFC 7518, Section 3.4 (validation)](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).
  • Список разрешённых алгоритмов

    from-key

    Алгоритм определяется ключом проверки подписи: токен RS256 при проверке открытым ключом EC отклонён с исключением UnsupportedJwtException.

    Что записала проверка
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: RS256 token, verifyWith(EC public key): UnsupportedJwtException: The parsed JWT indicates it was signed with the 'RS256' signature algorithm, but the provided sun.security.ec.ECPublicKeyImpl key may not be used to verify RS256 signatures.  Because the specified key reflects a specific and expected algorithm, and the JWT does not reflect this algorithm, it is likely that the JWT was not expected and therefore should not be trusted.  Another possibility is that the parser was provided the incorrect signature verification key, but this cannot be assumed for security reasons.
  • Неподписанные токены (alg none)

    opt-in

    parseSignedClaims отклоняет неподписанные токены; прочитать их можно через Jwts.parser().unsecured() с parseUnsecuredClaims.

    Что записала проверка
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: parseSignedClaims with the EC key: UnsupportedJwtException: Unsecured JWSs (those with an 'alg' (Algorithm) header value of 'none') are disallowed by default as mandated by https://www.rfc-editor.org/rfc/rfc7518.html#section-3.6. If you wish to allow them to be parsed, call the JwtParserBuilder.unsecured() method, but please read the security considerations covered in that method's JavaDoc before doing so. Header: {alg=none, typ=JWT}
    evidence jwt-alg-none: parser().unsecured().parseUnsecuredClaims: verified
  • Открытый ключ в роли секрета HMAC

    rejected

    Токен HS256, подписанный текстом PEM в качестве ключа, отклонён при проверке открытым ключом EC, но прошёл проверку с Keys.hmacShaKeyFor(pemBytes).

    Что записала проверка
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: verifyWith(EC public key): UnsupportedJwtException: The parsed JWT indicates it was signed with the 'HS256' signature algorithm, but the provided sun.security.ec.ECPublicKeyImpl key may not be used to verify HS256 signatures.  Because the specified key reflects a specific and expected algorithm, and the JWT does not reflect this algorithm, it is likely that the JWT was not expected and therefore should not be trusted.  Another possibility is that the parser was provided the incorrect signature verification key, but this cannot be assumed for security reasons.
    evidence jwt-hmac-public-key: verifyWith(Keys.hmacShaKeyFor(PEM bytes)): verified
  • Сверка кривой с alg

    enforced

    При подписи ES256 ключом P-384 возникает ошибка: the 'ES256' algorithm requires EC Keys with 256 bits.

    Что записала проверка
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: SignatureException: Unable to compute ES256 signature. Cause: The provided Elliptic Curve signing key size (aka order bit length) is 384 bits (48 bytes), but the 'ES256' algorithm requires EC Keys with 256 bits (32 bytes) per [RFC 7518, Section 3.4](https://www.rfc-editor.org/rfc/rfc7518.html#section-3.4).
  • Допустимое расхождение часов по умолчанию

    0s

    По умолчанию расхождение часов не допускается: токен, срок действия которого истёк 3 секунды назад, отклонён.

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

    no

    Токен без exp успешно разобран.

    Что записала проверка
    fact jwt-exp-required = no
  • Короткий секрет HMAC

    rejected

    Keys.hmacShaKeyFor отклонил 16-байтовый ключ с исключением WeakKeyException.

    Что записала проверка
    fact jwt-hmac-short-secret = rejected
    evidence jwt-hmac-short-secret: Keys.hmacShaKeyFor(16 bytes): WeakKeyException: The specified key byte array is 128 bits which is not secure enough for any JWT HMAC-SHA algorithm.  The JWT JWA Specification (RFC 7518, Section 3.2) states that keys used with HMAC-SHA algorithms MUST have a size >= 256 bits (the key size must be greater than or equal to the hash output size).  Consider using the Jwts.SIG.HS256.key() builder (or HS384.key() or HS512.key()) to create a key guaranteed to be secure enough for your preferred HMAC-SHA algorithm.  See https://tools.ietf.org/html/rfc7518#section-3.2 for more information.

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

Выпустите токен ES256 и разберите его с ключом, обязательным издателем и допуском на расхождение часов в 30 секунд.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import java.security.KeyPair;
import java.util.Date;

public class Usage {
    public static void main(String[] args) {
        KeyPair pair = Jwts.SIG.ES256.keyPair().build();
        String token = Jwts.builder()
            .subject("user-42")
            .issuer("https://auth.example.com")
            .expiration(new Date(System.currentTimeMillis() + 600_000))
            .signWith(pair.getPrivate(), Jwts.SIG.ES256)
            .compact();

        // The key decides which algorithms are acceptable; require iss and exp explicitly
        // and allow some clock skew (the default is none).
        Claims claims = Jwts.parser()
            .verifyWith(pair.getPublic())
            .requireIssuer("https://auth.example.com")
            .clockSkewSeconds(30)
            .build()
            .parseSignedClaims(token)
            .getPayload();
        System.out.println("sub: " + claims.getSubject() + ", has exp: " + (claims.getExpiration() != null));
    }
}

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

Подготовка
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
Команда
java -cp 'lib/*' Usage.java
Вывод должен совпадать (регулярное выражение)
sub: user-42, has exp: true

Библиотеки