java-jwt は JWT.create()…sign(algorithm) でトークンを作成し、JWT.require(algorithm)…build().verify(token) で検証します。Algorithm オブジェクトはアルゴリズムと鍵の両方を保持するため、検証器はその 1 つのアルゴリズムしか受け入れません。RS256 トークン、署名のないトークン、EC 鍵の PEM テキストで偽造した HS256 トークンは、いずれも ES256 の検証器に対して AlgorithmMismatchException で失敗しました。
この保護が有効なのは、Algorithm を設定から選んでいる場合だけです。JWT.require(Algorithm.none()) は署名のないトークンを受け入れ、Algorithm.HMAC256(pemBytes) は偽造した HS256 トークンの検証を通しました。トークンの alg ヘッダーをもとに Algorithm を選ぶコードや、鍵を生のバイト列として読み込むコードは、本来この API が防いでいるアルゴリズム混同のバグを再び引き起こします。JWT.decode() は署名もクレームもチェックせずに解析し、1 時間前に期限切れになったトークンもそのまま返しました。
対応アルゴリズムは HMAC256/384/512、RSA256/384/512、ECDSA256/384/512 で、PS256、ES256K、EdDSA はありません。そのため、RSA-PSS や Ed25519 に移行した発行者のトークンには Nimbus か JJWT が必要です。ES256 は署名時に P-384 の鍵を拒否します。
ES256 の署名は 64 バイトの生の r‖s で、DER の署名は汎用的な The Token's Signature resulted invalid when verified using the Algorithm: SHA256withECDSA で失敗します。署名の長さは JWT デバッガーで確認できます。デフォルトでは時刻のずれを許容せず、exp も任意です。サンプルでは、発行者のチェックに加えて withClaimPresence("exp") と acceptLeeway(30) を指定しています。