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

jose4j

Brian Campbell 开发的 jose4j,多款 Java 身份服务器所采用的 JOSE 库:JwtConsumer 默认屏蔽 none 并拒绝不匹配的密钥类型,但除非你加以限制,否则会接受该密钥能执行的其他任何算法。
许可证
Apache-2.0
检查的版本
0.9.7 ·
源代码
bitbucket.org/b_c/jose4j
文档
bitbucket.org/b_c/jose4j/wiki/Home

兼容性概览

JOSE 与 JWT

JWS 算法
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

jose4j 基于 JDK 自带的提供程序实现 JWS、JWE、JWK 和 JWT:用 JsonWebSignature 签名,用 JwtConsumerBuilder 构建的 JwtConsumer 完成验证和声明检查。0.9.7 对 256/384/512 的 HS、RS、PS、ES 以及 EdDSA 完成了往返测试;ES256K、Ed25519 和 Ed448 不是它能识别的算法名称。

默认设置挡住了两种经典攻击,但没有限定算法。除非显式关闭签名要求,否则 none 会被拒绝('none' is a blocked algorithm.);密钥类型也必须匹配:用 EC 密钥验证 RS256 令牌,以及以 EC 密钥的 PEM 文本为密钥的 HS256 令牌,都验证失败(cannot be used for HMAC verification)。不过,密钥能执行的其他任何算法都会被接受,因此要像示例那样,用 setJwsAlgorithmConstraints(ConstraintType.PERMIT, ...) 锁定算法。用 PEM 字节构造的 HmacKey 让那个伪造的 HS256 令牌验证通过了——密钥必须来自配置,绝不能根据令牌自己声称的内容来确定。

密钥长度会被强制检查:ES256 拒绝 P-384 密钥(expects a key using P-256 but was P-384),HS256 拒绝了 16 字节的密钥,因为 RFC 7518 要求密钥长度至少与哈希输出相同。ES256 签名是 64 字节的原始 r‖s;DER 签名会报错 Invalid JWS Signature——JWT 调试器会显示签名长度。

声明检查的默认值一严一松:不留时钟偏差容差,比大多数团队预想的更严格;不要求 exp,则更宽松。凡是读取其他服务所发令牌的 JwtConsumer,都应调用 setRequireExpirationTime()、setExpectedIssuer()、setExpectedAudience() 和 setAllowedClockSkewInSeconds()。

对于加密令牌,JWE 查看器可以显示 jose4j 生成的 JWE 包含哪些内容。

事实及其依据的检查

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

  • JWS 算法

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSA

    256/384/512 的 HS、RS、PS、ES 以及 EdDSA 的签名与验证均成功;ES256K、Ed25519 和 Ed448 抛出 InvalidAlgorithmException。

    检查记录
    fact jws-algorithms = HS256, HS384, HS512, RS256, RS384, RS512, PS256, PS384, PS512, ES256, ES384, ES512, EdDSA
    evidence jws-algorithms: ES256K: InvalidAlgorithmException: ES256K is an unknown, unsupported or unavailable alg algorithm (not one of [none, HS256, HS384, HS512, EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512]).
    evidence jws-algorithms: Ed25519: InvalidAlgorithmException: Ed25519 is an unknown, unsupported or unavailable alg algorithm (not one of [none, HS256, HS384, HS512, EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512]).
    evidence jws-algorithms: Ed448: InvalidAlgorithmException: Ed448 is an unknown, unsupported or unavailable alg algorithm (not one of [none, HS256, HS384, HS512, EdDSA, ES256, ES384, ES512, RS256, RS384, RS512, PS256, PS384, PS512]).
  • ES256 签名编码

    raw

    ES256 签名是 64 字节的原始 r‖s。

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

    rejected

    DER 编码的 ES256 签名会报错 InvalidJwtSignatureException: JWT rejected due to invalid signature。

    检查记录
    fact jws-der-signature = rejected
    evidence jws-der-signature: InvalidJwtSignatureException: JWT rejected due to invalid signature. Additional details: [[9] Invalid JWS Signature:
  • 算法允许列表

    from-key

    不设置算法约束时,由密钥类型决定:用 EC 密钥验证 RS256 令牌,报错 The given key (algorithm=EC) is not valid for SHA256withRSA。

    检查记录
    fact jwt-algorithm-allowlist = from-key
    evidence jwt-algorithm-allowlist: JwtConsumer without algorithm constraints, RS256 token against an EC key: InvalidJwtException: JWT processing failed. Additional details: [[17] Unable to process JOSE object (cause: org.jose4j.lang.InvalidKeyException: The given key (algorithm=EC) is not valid for SHA256withRSA):
  • 未签名的令牌(alg none)

    opt-in

    默认的 JwtConsumer 会拒绝未签名的令牌,报错 'none' is a blocked algorithm.;调用 setDisableRequireSignature() 且不设置算法约束时,则会接受这类令牌。

    检查记录
    fact jwt-alg-none = opt-in
    evidence jwt-alg-none: default JwtConsumer: InvalidJwtException: JWT processing failed. Additional details: [[17] Unable to process JOSE object (cause: org.jose4j.lang.InvalidAlgorithmException: 'none' is a blocked algorithm.):
    evidence jwt-alg-none: setDisableRequireSignature with no algorithm constraints: verified
  • 把公钥用作 HMAC 密钥

    rejected

    以 PEM 文本为密钥的 HS256 令牌,用 EC 公钥验证失败,但用 PEM 字节构造的 HmacKey 让它验证通过了。

    检查记录
    fact jwt-hmac-public-key = rejected
    evidence jwt-hmac-public-key: JwtConsumer with the EC public key: InvalidJwtException: JWT processing failed. Additional details: [[17] Unable to process JOSE object (cause: org.jose4j.lang.InvalidKeyException: class sun.security.ec.ECPublicKeyImpl cannot be used for HMAC verification.):
    evidence jwt-hmac-public-key: JwtConsumer with an HmacKey built from the PEM bytes: verified
  • 按 alg 检查曲线

    enforced

    用 P-384 密钥签名 ES256 时报错 ES256/SHA256withECDSA expects a key using P-256 but was P-384。

    检查记录
    fact jwt-ecdsa-curve-check = enforced
    evidence jwt-ecdsa-curve-check: InvalidKeyException: ES256/SHA256withECDSA expects a key using P-256 but was P-384
  • 默认时钟偏差

    0s

    默认没有时钟偏差容差:三秒前过期的令牌被拒绝。

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

    no

    不含 exp 的令牌被接受;setRequireExpirationTime() 会将其设为必需。

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

    rejected

    HS256 拒绝了 16 字节的密钥:A key of the same size as the hash output ... or larger MUST be used。

    检查记录
    fact jwt-hmac-short-secret = rejected
    evidence jwt-hmac-short-secret: HS256 with a 16-byte key: InvalidKeyException: A key of the same size as the hash output (i.e. 256 bits for HS256) or larger MUST be used with the HMAC SHA algorithms but this key is only 128 bits

用法

用生成的 JWK 对 ES256 令牌签名,再在锁定算法、要求颁发者和 exp、允许 30 秒时钟偏差的条件下读取并验证它。
import org.jose4j.jwa.AlgorithmConstraints.ConstraintType;
import org.jose4j.jwk.EcJwkGenerator;
import org.jose4j.jwk.EllipticCurveJsonWebKey;
import org.jose4j.jws.AlgorithmIdentifiers;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.jwt.consumer.JwtConsumer;
import org.jose4j.jwt.consumer.JwtConsumerBuilder;
import org.jose4j.keys.EllipticCurves;

public class Usage {
    public static void main(String[] args) throws Exception {
        EllipticCurveJsonWebKey key = EcJwkGenerator.generateJwk(EllipticCurves.P256);
        JwtClaims claims = new JwtClaims();
        claims.setSubject("user-42");
        claims.setIssuer("https://auth.example.com");
        claims.setExpirationTimeMinutesInTheFuture(10);

        JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(claims.toJson());
        jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256);
        jws.setKey(key.getPrivateKey());

        // Pin the algorithm and require exp; the defaults would allow any non-'none' alg for the key.
        JwtConsumer consumer = new JwtConsumerBuilder()
            .setJwsAlgorithmConstraints(ConstraintType.PERMIT, AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256)
            .setVerificationKey(key.getPublicKey())
            .setExpectedIssuer("https://auth.example.com")
            .setRequireExpirationTime()
            .setAllowedClockSkewInSeconds(30)
            .build();
        System.out.println("sub: " + consumer.processToClaims(jws.getCompactSerialization()).getSubject());
    }
}

代码片段测试工具会在容器镜像 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

库