java-jwt создаёт токены через JWT.create()…sign(algorithm) и проверяет их через JWT.require(algorithm)…build().verify(token). Объект Algorithm содержит и алгоритм, и ключ, поэтому объект проверки принимает только этот один алгоритм: токен RS256, неподписанный токен и токен HS256, подделанный с помощью текста PEM ключа EC, не прошли проверку объектом для ES256 с ошибкой AlgorithmMismatchException.
Защита работает, только пока Algorithm выбирается по конфигурации. JWT.require(Algorithm.none()) принимает неподписанные токены, а с Algorithm.HMAC256(pemBytes) поддельный токен HS256 прошёл проверку: код, который выбирает Algorithm по заголовку alg токена или загружает ключи как сырые байты, воспроизводит ошибки подмены алгоритма, от которых API в остальном защищает. JWT.decode() разбирает токен, не проверяя ни подпись, ни утверждения; он вернул токен, срок действия которого истёк час назад.
Набор алгоритмов: HMAC256/384/512, RSA256/384/512 и ECDSA256/384/512, без PS256, ES256K и EdDSA, поэтому для токенов от издателей, перешедших на RSA-PSS или Ed25519, нужны Nimbus или JJWT. ES256 не принимает ключ P-384 уже на этапе подписи.
Подписи ES256 — это 64-байтовый формат raw r‖s, а подпись DER отклоняется с общей ошибкой The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA; длину подписи покажет отладчик JWT. Допуска на расхождение часов по умолчанию нет, а exp необязателен: пример добавляет withClaimPresence("exp") и acceptLeeway(30) вместе с проверкой издателя.