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

Nimbus JOSE + JWT

Connect2id 的 Nimbus JOSE + JWT,Spring Security 和许多 Java OAuth 服务器内部使用的 JOSE 库:处理器的密钥选择器就是算法允许列表,未签名的令牌会被拒绝,默认时钟偏差容差为 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 的资源服务器就构建在它之上,因此它的默认设置也就是大多数 Spring API 实际执行的规则。

使用处理器时,JWSVerificationKeySelector 就是允许列表:配置为 ES256 后,它拒绝了 RS256 令牌,也拒绝了以 EC 密钥的 PEM 文本为密钥的 HS256 令牌,报错 Another algorithm expected, or no matching key(s) found;不配置任何选择器时,处理器会拒绝所有令牌。未签名的令牌会被拒绝(Unsecured (plain) JWTs are rejected, extend class to handle)。底层 API 没有这种保护——用 PEM 文本构建的 MACVerifier 让那个伪造的 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 需要 classpath 中有 Google Tink:缺少它时,签名报错 NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair。ES256K 需要支持 secp256k1 的提供程序,例如 Bouncy Castle,因为 JDK 中没有这样的提供程序。

要查看身份提供商发布了什么,可以使用 JWKS 检测;对于加密令牌,可以使用 JWE 查看器。

事实及其依据的检查

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

  • JWS 算法

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

    在 JDK 上,256/384/512 的 HS、RS、PS 和 ES 以及 EdDSA 和 Ed25519 的签名与验证均成功;使用 JDK 自带的提供程序时,ES256K 和 Ed448 未能完成往返测试。

    检查记录
    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 字节的原始 r‖s。

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

    rejected

    带有 DER 编码 ES256 签名的令牌验证失败。

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

    以 PEM 文本为密钥的 HS256 令牌被配置为 ES256 的处理器拒绝,但用该 PEM 文本手动构建的 MACVerifier 让它验证通过了。

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

    用 P-384 密钥签名 ES256 时报错 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]
  • 不依赖 Google Tink 的 Ed25519

    fails

    classpath 中没有 Google Tink 时,用 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

用法

签发带密钥 ID 的 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

库