跳到主要内容
ecdsa.com
Java · Kotlin

JJWT (io.jsonwebtoken)

Les Hazlewood 开发的 JJWT,广泛用于 Java 和 Android 的 JWT 库:验证密钥决定可接受的算法,未签名的令牌需要显式的 unsecured() 解析器,弱 HMAC 密钥会被直接拒绝。
许可证
Apache-2.0
检查的版本
0.13.0 ·
源代码
github.com/jwtk/jjwt
文档
github.com/jwtk/jjwt#readme

兼容性概览

JOSE 与 JWT

JWS 算法
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

JJWT 0.12 及更高版本提供流式的构建器和解析器——Jwts.builder()…signWith(key, alg) 和 Jwts.parser().verifyWith(key)…parseSignedClaims(token)——算法集中在 Jwts.SIG 中。它分为三个制品发布:编译时需要 jjwt-api,运行时需要 jjwt-impl 和 jjwt-jackson 之类的 JSON 绑定。0.13.0 对 256/384/512 的 HS、RS、PS、ES 以及 EdDSA 完成了往返测试;ES256K 以及 Ed25519 和 Ed448 这两个名称不在 Jwts.SIG 中。

解析器根据验证密钥推导出允许的算法,错误信息也会直接说明这一点。用 EC 公钥验证 RS256 令牌,以及以 EC 密钥的 PEM 文本为密钥的 HS256 令牌,都报错 UnsupportedJwtException,并说明该密钥不能用于这个算法。起防护作用的是密钥类型,而不是列表:传入 Keys.hmacShaKeyFor(pemBytes) 的解析器让那个伪造的令牌验证通过了,因此切勿用可能公开的材料构造 HMAC 密钥。

parseSignedClaims 会拒绝未签名的令牌,错误信息引用 RFC 7518 §3.6;这类令牌只能通过 Jwts.parser().unsecured()…parseUnsecuredClaims 读取。弱 HMAC 密钥根本走不到这一步:Keys.hmacShaKeyFor 以 WeakKeyException 拒绝了 16 字节的密钥,因为 RFC 7518 要求密钥长度至少与哈希长度相同。ES256 在签名时会拒绝 P-384 密钥。

有两个默认行为需要注意。没有时钟偏差容差——三秒前过期的令牌验证失败——而且 exp 是可选的,因此要像示例那样设置 clockSkewSeconds,并检查过期时间和颁发者。ES256 签名是 64 字节的原始 r‖s;DER 签名会得到准确的报错(Provided signature is 576 bits (72 bytes) but ES256 signatures must be exactly 512 bits (64 bytes)),它是少数能指出编码问题的库之一。可以在 JWT 调试器中检查令牌,用 JWT 密钥检测工具检查 HMAC 密钥。

如需大规模处理 JWE 和 JWK 集,可以与 Nimbus JOSE + JWT 比较。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/jjwt/run.sh 于2026年10月20日在容器镜像 maven:3.9.16-eclipse-temurin-25 中运行了 JJWT (io.jsonwebtoken) 0.13.0;本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • JWS 算法

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    256/384/512 的 HS、RS、PS 和 ES 以及 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 字节的原始 r‖s。

    检查记录
    fact jws-ecdsa-encoding = raw
  • JWS 中的 DER 签名

    rejected

    DER 编码的 ES256 签名会报错 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

    由验证密钥决定算法:用 EC 公钥验证 RS256 令牌,报错 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

    以 PEM 文本为密钥的 HS256 令牌,用 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

    用 P-384 密钥签名 ES256 会失败: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

    默认不允许时钟偏差:三秒前过期的令牌被拒绝。

    检查记录
    fact jwt-clock-skew = 0s
  • 是否要求过期时间声明

    no

    不含 exp 的令牌能成功解析。

    检查记录
    fact jwt-exp-required = no
  • 过短的 HMAC 密钥

    rejected

    Keys.hmacShaKeyFor 以 WeakKeyException 拒绝了 16 字节的密钥。

    检查记录
    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

库