java-jwt construye los tokens con JWT.create()…sign(algorithm) y los verifica con JWT.require(algorithm)…build().verify(token). El objeto Algorithm lleva tanto el algoritmo como la clave, así que un verificador solo acepta ese algoritmo: un token RS256, un token sin firmar y un token HS256 falsificado con el texto PEM de la clave EC fallaron todos contra un verificador ES256 con AlgorithmMismatchException.
La protección solo se mantiene mientras el Algorithm se elija a partir de la configuración. JWT.require(Algorithm.none()) acepta tokens sin firmar, y Algorithm.HMAC256(pemBytes) verificó el token HS256 falsificado: el código que elige el Algorithm según el encabezado alg del token, o que carga las claves como bytes sin más, reproduce los fallos de confusión de algoritmos que la API evita en otro caso. JWT.decode() analiza sin comprobar la firma ni ningún claim; devolvió un token que había caducado una hora antes.
El conjunto de algoritmos es HMAC256/384/512, RSA256/384/512 y ECDSA256/384/512 —sin PS256, sin ES256K y sin EdDSA—, así que los tokens de emisores que ya usan RSA-PSS o Ed25519 necesitan Nimbus o JJWT. ES256 rechaza una clave P-384 al firmar.
Las firmas ES256 son el r‖s en bruto de 64 bytes, y una firma DER falla con el genérico The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA; el decodificador de JWT muestra la longitud de la firma. No hay tolerancia predeterminada y exp es opcional: el ejemplo añade withClaimPresence("exp") y acceptLeeway(30) junto con la comprobación del emisor.