O java-jwt cria tokens com JWT.create()…sign(algorithm) e os verifica com JWT.require(algorithm)…build().verify(token). O objeto Algorithm carrega tanto o algoritmo quanto a chave, então um verificador só aceita aquele único algoritmo: um token RS256, um token sem assinatura e um token HS256 forjado com o texto PEM da chave EC falharam contra um verificador ES256 com AlgorithmMismatchException.
A proteção só vale enquanto o Algorithm for escolhido a partir da configuração. JWT.require(Algorithm.none()) aceita tokens sem assinatura, e Algorithm.HMAC256(pemBytes) verificou o token HS256 forjado — um código que escolhe o Algorithm a partir do cabeçalho alg do token, ou que carrega chaves como bytes brutos, recria os bugs de confusão de algoritmo que a API normalmente evita. JWT.decode() analisa o token sem verificar a assinatura nem nenhuma claim; ele retornou um token que tinha expirado uma hora antes.
O conjunto de algoritmos é HMAC256/384/512, RSA256/384/512 e ECDSA256/384/512 — sem PS256, sem ES256K e sem EdDSA —, então tokens de emissores que migraram para RSA-PSS ou Ed25519 precisam do Nimbus ou do JJWT. O ES256 recusa uma chave P-384 no momento da assinatura.
As assinaturas ES256 são o r‖s bruto de 64 bytes, e uma assinatura DER falha com a mensagem genérica The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA; o depurador de JWT mostra o tamanho da assinatura. Não há tolerância padrão e exp é opcional: o exemplo acrescenta withClaimPresence("exp") e acceptLeeway(30), além da verificação do emissor.