Zum Inhalt springen
ecdsa.com
Java · Kotlin

JJWT (io.jsonwebtoken)

JJWT von Les Hazlewood, eine verbreitete JWT-Bibliothek für Java und Android: Der Prüfschlüssel legt den zulässigen Algorithmus fest, unsignierte Tokens brauchen einen ausdrücklich mit unsecured() erstellten Parser, und schwache HMAC-Schlüssel werden rundweg abgelehnt.
Lizenz
Apache-2.0
Geprüfte Version
0.13.0 ·
Quellcode
github.com/jwtk/jjwt
Dokumentation
github.com/jwtk/jjwt#readme

Kompatibilität auf einen Blick

JOSE und JWT

JWS-Algorithmen
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

Ab JJWT 0.12 gibt es eine Fluent-API mit Builder und Parser – Jwts.builder()…signWith(key, alg) und Jwts.parser().verifyWith(key)…parseSignedClaims(token) –, und Jwts.SIG enthält die Algorithmen. Die Bibliothek wird als drei Artefakte ausgeliefert: jjwt-api zur Kompilierzeit, jjwt-impl und eine JSON-Bindung wie jjwt-jackson zur Laufzeit. Version 0.13.0 bestand den Roundtrip mit HS, RS, PS und ES bei 256/384/512 sowie mit EdDSA; ES256K und die Namen Ed25519 und Ed448 fehlen in Jwts.SIG.

Der Parser leitet den zulässigen Algorithmus aus dem Prüfschlüssel ab, und seine Fehlermeldungen sagen das unmissverständlich. Ein RS256-Token, geprüft mit einem öffentlichen Schlüssel vom Typ EC, und ein HS256-Token mit dem PEM-Text dieses EC-Schlüssels als Schlüssel scheiterten beide mit UnsupportedJwtException und dem Hinweis, dass der Schlüssel für diesen Algorithmus nicht verwendet werden darf. Der Schutz ist der Schlüsseltyp, keine Liste: Mit Keys.hmacShaKeyFor(pemBytes) als Schlüssel bestand dieses gefälschte Token die Prüfung des Parsers – baue HMAC-Schlüssel also nie aus Material, das öffentlich sein könnte.

Unsignierte Tokens lehnt parseSignedClaims mit einer Meldung ab, die auf RFC 7518 §3.6 verweist; lesbar sind sie nur über Jwts.parser().unsecured()…parseUnsecuredClaims. Schwache HMAC-Schlüssel kommen gar nicht so weit: Keys.hmacShaKeyFor lehnte einen 16-Byte-Schlüssel mit WeakKeyException ab, denn RFC 7518 verlangt Schlüssel, die mindestens so lang sind wie der Hash. ES256 lehnt einen P-384-Schlüssel schon beim Signieren ab.

Zwei Standardeinstellungen verdienen Aufmerksamkeit. Es gibt keine Toleranz für Uhrenabweichungen (Clock Skew) – ein Token, das drei Sekunden zuvor abgelaufen war, scheiterte –, und exp ist optional; setze also clockSkewSeconds und prüfe Ablauf und Aussteller, wie es das Beispiel tut. ES256-Signaturen sind das 64 Byte lange rohe r‖s; eine DER-Signatur wird präzise gemeldet (Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes)) – JJWT ist eine der wenigen Bibliotheken, die das Kodierungsproblem benennen. Untersuche Tokens im JWT-Debugger und HMAC-Schlüssel mit dem JWT-Secret-Checker.

Für die Verarbeitung von JWE und JWK-Sets in großem Umfang lohnt sich ein Vergleich mit Nimbus JOSE + JWT.

Fakten und die Prüfungen dahinter

Nichts im Folgenden ist aus einer Dokumentation abgeschrieben. Das Prüfskript content/libraries/verify/jjwt/run.sh hat JJWT (io.jsonwebtoken) 0.13.0 am 20. Oktober 2026 im Container-Image maven:3.9.16-eclipse-temurin-25 ausgeführt; die Seite muss mit seiner Ausgabe übereinstimmen, und jeder Fakt führt die zugehörigen Zeilen auf.

  • JWS-Algorithmen

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    Signieren und Prüfen funktionierten mit HS, RS, PS und ES bei 256/384/512 sowie mit EdDSA; ES256K, Ed25519 und Ed448 fehlen in Jwts.SIG.

    Von der Prüfung aufgezeichnet
    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
  • Kodierung von ES256-Signaturen

    raw

    ES256-Signaturen bestehen aus 64 Byte rohem r‖s.

    Von der Prüfung aufgezeichnet
    fact jws-ecdsa-encoding = raw
  • DER-Signatur in einer JWS

    rejected

    Eine DER-kodierte ES256-Signatur scheitert mit Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes).

    Von der Prüfung aufgezeichnet
    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).
  • Allowlist der Algorithmen

    from-key

    Der Prüfschlüssel bestimmt den Algorithmus: Ein RS256-Token scheiterte an einem öffentlichen Schlüssel vom Typ EC mit UnsupportedJwtException.

    Von der Prüfung aufgezeichnet
    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.
  • Unsignierte Tokens (alg none)

    opt-in

    parseSignedClaims lehnt unsignierte Tokens ab; Jwts.parser().unsecured() mit parseUnsecuredClaims liest sie.

    Von der Prüfung aufgezeichnet
    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
  • Öffentlicher Schlüssel als HMAC-Secret

    rejected

    Ein HS256-Token mit dem PEM-Text als Schlüssel wurde mit dem öffentlichen Schlüssel vom Typ EC abgelehnt, bestand mit Keys.hmacShaKeyFor(pemBytes) aber die Prüfung.

    Von der Prüfung aufgezeichnet
    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
  • Abgleich der Kurve mit alg

    enforced

    Das Signieren mit ES256 und einem P-384-Schlüssel scheitert: the 'ES256' algorithm requires EC Keys with 256 bits.

    Von der Prüfung aufgezeichnet
    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).
  • Standardtoleranz für Uhrabweichungen

    0s

    Standardmäßig keine Toleranz für Uhrenabweichungen (Clock Skew): Ein Token, das drei Sekunden zuvor abgelaufen war, wurde abgelehnt.

    Von der Prüfung aufgezeichnet
    fact jwt-clock-skew = 0s
  • Ablauf-Claim erforderlich

    no

    Ein Token ohne exp wurde erfolgreich geparst.

    Von der Prüfung aufgezeichnet
    fact jwt-exp-required = no
  • Kurzes HMAC-Secret

    rejected

    Keys.hmacShaKeyFor lehnte einen 16-Byte-Schlüssel mit WeakKeyException ab.

    Von der Prüfung aufgezeichnet
    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.

Verwendung

Stelle ein ES256-Token aus und parse es mit dem Schlüssel, einem vorgeschriebenen Aussteller und 30 Sekunden Toleranz für Uhrenabweichungen.
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));
    }
}

Die Snippet-Testumgebung führt jedes Beispiel unverändert im Container-Image maven:3.9.16-eclipse-temurin-25 aus, und der Lauf schlägt fehl, wenn die Ausgabe nicht übereinstimmt.

Einrichtung
mvn -q -B dependency:copy-dependencies -DoutputDirectory=lib >/dev/null
Befehl
java -cp 'lib/*' Usage.java
Ausgabe muss passen (regulärer Ausdruck)
sub: user-42, has exp: true

Bibliotheken