Pular para o conteúdo
ecdsa.com
Java · Kotlin

Nimbus JOSE + JWT

O Nimbus JOSE + JWT, da Connect2id, a biblioteca JOSE dentro do Spring Security e de muitos servidores OAuth em Java: um processador cujo seletor de chaves é a lista de algoritmos permitidos, tokens sem assinatura recusados e 60 segundos de tolerância padrão para diferença de relógio.
Licença
Apache-2.0
Versão verificada
10.10 ·
Código-fonte
bitbucket.org/connect2id/nimbus-jose-jwt
Documentação
connect2id.com/products/nimbus-jose-jwt

Visão geral da compatibilidade

JOSE e JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

O Nimbus cobre JWS, JWE, JWK e JWT em duas camadas: objetos de baixo nível (SignedJWT, ECDSASigner, MACVerifier) e o DefaultJWTProcessor, que une a seleção de chaves, a verificação da assinatura e a verificação das claims. O servidor de recursos do Spring Security é construído sobre ele, então os padrões dele são o que a maioria das APIs Spring aplica.

Com o processador, o JWSVerificationKeySelector é a lista de algoritmos permitidos: configurado para ES256, ele recusou com Another algorithm expected, or no matching key(s) found um token RS256 e um token HS256 que usava como chave o texto PEM da chave EC, e, sem nenhum seletor, recusa tudo. Tokens sem assinatura são rejeitados (Unsecured (plain) JWTs are rejected, extend class to handle). A API de baixo nível não tem essa proteção — um MACVerifier criado a partir do texto PEM verificou aquele token HS256 forjado —, então um código que escolhe o verificador pelo cabeçalho do token recria o clássico bug de confusão de algoritmo. Use o processador, como faz o exemplo.

DefaultJWTClaimsVerifier tolera 60 segundos de diferença de relógio por padrão, e exp é opcional, a menos que esteja listada como claim obrigatória — o exemplo exige sub e exp e fixa o emissor. O ES256 recusa uma chave P-384 (The ES256 algorithm is not allowed or supported by the JWS signer), e uma assinatura codificada em DER simplesmente não passa na verificação; confira o comprimento da assinatura no depurador de JWT.

A versão 10.10 fez o ciclo completo de ida e volta com as famílias HS, RS, PS e ES, além do EdDSA e do nome mais recente Ed25519. O Ed25519 precisa do Google Tink no classpath: sem ele, a assinatura falhou com NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair. O ES256K precisa de um provedor com secp256k1, como o Bouncy Castle, já que o JDK não tem nenhum.

Para conferir o que um provedor de identidade publica, use o verificador de JWKS; para tokens criptografados, o inspetor de JWE.

Os fatos e as verificações por trás deles

Nada abaixo foi copiado da documentação. A verificação content/libraries/verify/nimbus-jose-jwt/run.sh executou Nimbus JOSE + JWT 10.10 na imagem de contêiner maven:3.9.16-eclipse-temurin-25 em 20 de outubro de 2026; a página precisa corresponder ao que a verificação imprimiu, e cada fato lista essas linhas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

    Assinatura e verificação funcionaram no JDK com HS, RS, PS e ES em 256/384/512, EdDSA e Ed25519; ES256K e Ed448 não completaram o ciclo de ida e volta com os provedores do JDK.

    Registrado pela verificação
    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
  • Codificação da assinatura ES256

    raw

    As assinaturas ES256 são 64 bytes de r‖s brutos.

    Registrado pela verificação
    fact jws-ecdsa-encoding = raw
  • Assinatura DER dentro de um JWS

    rejected

    Um token com uma assinatura ES256 codificada em DER falhou na verificação.

    Registrado pela verificação
    fact jws-der-signature = rejected
    evidence jws-der-signature: not verified
  • Lista de algoritmos permitidos

    required

    DefaultJWTProcessor precisa de um seletor de chaves: sem ele, falha com No JWS key selector is configured, e um seletor para ES256 rejeitou um token RS256.

    Registrado pela verificação
    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
  • Tokens sem assinatura (alg none)

    rejected

    O processador rejeita tokens sem assinatura com Unsecured (plain) JWTs are rejected, extend class to handle.

    Registrado pela verificação
    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
  • Chave pública usada como segredo HMAC

    rejected

    Um token HS256 que usava o texto PEM como chave foi rejeitado pelo processador configurado para ES256, mas um MACVerifier criado manualmente a partir desse texto PEM o verificou.

    Registrado pela verificação
    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
  • Curva conferida com o alg

    enforced

    Assinar ES256 com uma chave P-384 falha com The ES256 algorithm is not allowed or supported by the JWS signer: Supported algorithms: [ES384].

    Registrado pela verificação
    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]
  • Tolerância padrão de diferença de relógio

    60s

    DefaultJWTClaimsVerifier tolera 60 segundos de diferença de relógio (DEFAULT_MAX_CLOCK_SKEW_SECONDS).

    Registrado pela verificação
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: DefaultJWTClaimsVerifier.DEFAULT_MAX_CLOCK_SKEW_SECONDS = 60
  • Claim de expiração obrigatória

    no

    Um token sem exp passou pelo verificador de claims padrão; listar exp como obrigatória o fez falhar com JWT missing required claims: [exp].

    Registrado pela verificação
    fact jwt-exp-required = no
    evidence jwt-exp-required: with DefaultJWTClaimsVerifier requiring exp: BadJWTException: JWT missing required claims: [exp]
  • Ed25519 sem o Google Tink

    fails

    Sem o Google Tink no classpath, a assinatura com Ed25519Signer falhou com NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair.

    Registrado pela verificação
    fact ed25519-without-tink = fails
    evidence ed25519-without-tink: NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair

Uso

Assine um token ES256 com um ID de chave e processe-o com um seletor de chaves só para ES256 e um verificador de claims que exige sub, exp e o emissor.
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());
    }
}

O executor de trechos de código roda cada exemplo literalmente na imagem de contêiner maven:3.9.16-eclipse-temurin-25, e a execução falha se a saída não corresponder.

Configuração
mvn -q -B dependency:copy -Dartifact=com.nimbusds:nimbus-jose-jwt:10.10 -DoutputDirectory=lib >/dev/null
Comando
java -cp lib/nimbus-jose-jwt-10.10.jar Usage.java
A saída deve corresponder a (expressão regular)
sub: user-42

Bibliotecas