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.