Saltar al contenido
ecdsa.com
Java · Kotlin

Nimbus JOSE + JWT

Nimbus JOSE + JWT de Connect2id, la biblioteca JOSE que llevan dentro Spring Security y muchos servidores OAuth en Java: un procesador cuyo selector de claves es la lista de algoritmos permitidos, rechazo de los tokens sin firmar y 60 segundos de desfase de reloj admitido por defecto.
Licencia
Apache-2.0
Versión comprobada
10.10 ·
Código fuente
bitbucket.org/connect2id/nimbus-jose-jwt
Documentación
connect2id.com/products/nimbus-jose-jwt

Compatibilidad de un vistazo

JOSE y JWT

Algoritmos JWS
HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

Nimbus cubre JWS, JWE, JWK y JWT en dos capas: objetos de bajo nivel (SignedJWT, ECDSASigner, MACVerifier) y el DefaultJWTProcessor, que reúne la selección de claves, la comprobación de la firma y la de los claims. El servidor de recursos de Spring Security se basa en él, así que sus valores predeterminados son los que aplica la mayoría de las API de Spring.

Con el procesador, el JWSVerificationKeySelector es la lista de algoritmos permitidos: configurado para ES256, rechazó un token RS256 y un token HS256 con el texto PEM de la clave EC como clave, con Another algorithm expected, or no matching key(s) found, y sin ningún selector lo rechaza todo. Los tokens sin firmar se rechazan (Unsecured (plain) JWTs are rejected, extend class to handle). La API de bajo nivel no tiene esa protección —un MACVerifier construido a partir del texto PEM verificó ese token HS256 falsificado—, así que el código que elige un verificador según el encabezado del token reproduce el clásico error de confusión de algoritmos. Usa el procesador, como hace el ejemplo.

DefaultJWTClaimsVerifier admite por defecto 60 segundos de desfase de reloj, y exp es opcional salvo que figure como claim obligatorio; el ejemplo exige sub y exp y fija el emisor. ES256 rechaza una clave P-384 (The ES256 algorithm is not allowed or supported by the JWS signer), y una firma codificada en DER simplemente no se verifica; comprueba la longitud de la firma con el decodificador de JWT.

La versión 10.10 superó la prueba de ida y vuelta con las familias HS, RS, PS y ES, además de EdDSA y el nombre más reciente Ed25519. Ed25519 necesita Google Tink en el classpath: sin él, la operación de firma falló con NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair. ES256K necesita un proveedor con secp256k1, como Bouncy Castle, porque el JDK no tiene ninguno.

Para comprobar qué publica un proveedor de identidad, usa el comprobador de JWKS; para los tokens cifrados, el inspector de JWE.

Datos y las comprobaciones que los respaldan

Nada de lo que sigue está copiado de la documentación. La comprobación content/libraries/verify/nimbus-jose-jwt/run.sh ejecutó Nimbus JOSE + JWT 10.10 en la imagen de contenedor maven:3.9.16-eclipse-temurin-25 el 20 de octubre de 2026; la página debe coincidir con lo que imprimió, y cada dato cita esas líneas.

  • Algoritmos JWS

    HS256HS384HS512RS256RS384RS512PS256PS384PS512ES256ES384ES512EdDSAEd25519

    Con HS, RS, PS y ES de 256/384/512, EdDSA y Ed25519 se firmó y se verificó en el JDK; ES256K y Ed448 no superaron la prueba de ida y vuelta con los proveedores del JDK.

    Registrado por la comprobación
    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
  • Codificación de la firma ES256

    raw

    Las firmas ES256 son 64 bytes de r‖s en bruto.

    Registrado por la comprobación
    fact jws-ecdsa-encoding = raw
  • Firma DER dentro de un JWS

    rejected

    Un token con una firma ES256 codificada en DER no superó la verificación.

    Registrado por la comprobación
    fact jws-der-signature = rejected
    evidence jws-der-signature: not verified
  • Lista de algoritmos permitidos

    required

    DefaultJWTProcessor necesita un selector de claves: sin él falla con No JWS key selector is configured, y un selector para ES256 rechazó un token RS256.

    Registrado por la comprobación
    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 sin firma (alg none)

    rejected

    El procesador rechaza los tokens sin firmar con Unsecured (plain) JWTs are rejected, extend class to handle.

    Registrado por la comprobación
    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
  • Clave pública usada como secreto HMAC

    rejected

    El procesador configurado para ES256 rechazó un token HS256 con el texto PEM como clave, pero un MACVerifier construido a mano a partir de ese texto PEM lo verificó.

    Registrado por la comprobación
    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 cotejada con alg

    enforced

    Firmar ES256 con una clave P-384 falla con The ES256 algorithm is not allowed or supported by the JWS signer: Supported algorithms: [ES384].

    Registrado por la comprobación
    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]
  • Desfase de reloj predeterminado

    60s

    DefaultJWTClaimsVerifier admite 60 segundos (DEFAULT_MAX_CLOCK_SKEW_SECONDS).

    Registrado por la comprobación
    fact jwt-clock-skew = 60s
    evidence jwt-clock-skew: DefaultJWTClaimsVerifier.DEFAULT_MAX_CLOCK_SKEW_SECONDS = 60
  • Claim de caducidad obligatorio

    no

    Un token sin exp superó el verificador de claims predeterminado; al incluir exp como obligatorio, falló con JWT missing required claims: [exp].

    Registrado por la comprobación
    fact jwt-exp-required = no
    evidence jwt-exp-required: with DefaultJWTClaimsVerifier requiring exp: BadJWTException: JWT missing required claims: [exp]
  • Ed25519 sin Google Tink

    fails

    Sin Google Tink en el classpath, firmar con Ed25519Signer falló con NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair.

    Registrado por la comprobación
    fact ed25519-without-tink = fails
    evidence ed25519-without-tink: NoClassDefFoundError: com/google/crypto/tink/subtle/Ed25519Sign$KeyPair

Uso

Firma un token ES256 con un ID de clave y procésalo con un selector de claves solo para ES256 y un verificador de claims que exige sub, exp y el emisor.
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());
    }
}

El arnés de pruebas de fragmentos de código ejecuta cada ejemplo tal cual en la imagen de contenedor maven:3.9.16-eclipse-temurin-25, y la ejecución falla si la salida no coincide.

Configuración
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
La salida debe coincidir (expresión regular)
sub: user-42

Bibliotecas