RHEL8下生成JWT签名报错InvalidKeyException求助
问题重现
签名JWT时抛出如下异常:
io.jsonwebtoken.security.InvalidKeyException: JWT standard signing algorithms require either 1) a SecretKey for HMAC-SHA algorithms or 2) a private RSAKey for RSA algorithms or 3) a private ECKey for Elliptic Curve algorithms. The specified key is of type sun.security.pkcs11.P11Key$P11PrivateKey
报错代码:
public static String createJwtToken(Key privKey, String iss, String[] roles) { long nowMs = System.currentTimeMillis(); long ttl = 60*60*1000; // 1 hour Date now = new Date(nowMs); Date exp = new Date(nowMs+ttl); return "Bearer " + Jwts.builder() .setHeaderParam("typ", "JWT") .claim("roles", roles) .setIssuer(iss).setAudience("DRF") .setIssuedAt(now) .setExpiration(exp) .signWith(privKey) .compact(); }
私钥生成代码:
KeyFactory kf = KeyFactory.getInstance("EC"); EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); PrivateKey privateKey = kf.generatePrivate(keySpec);
环境差异:RHEL7+Java8正常,升级到RHEL8(OpenJDK8/11)后触发错误。
问题原因
RHEL8的OpenJDK默认调高了PKCS11安全提供者的优先级,从PKCS8字节生成EC私钥时,会优先通过PKCS11提供者创建sun.security.pkcs11.P11Key$P11PrivateKey实例。而JJWT的密钥校验逻辑仅识别直接实现ECKey接口的密钥类型,导致校验失败。
解决方案
方案1:指定密钥工厂使用SunEC提供者
修改私钥生成代码,明确指定使用SunEC提供者生成EC密钥,绕过PKCS11:
// 指定SunEC提供者,避免PKCS11生成的密钥类型 KeyFactory kf = KeyFactory.getInstance("EC", "SunEC"); EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); PrivateKey privateKey = kf.generatePrivate(keySpec);
方案2:显式指定签名算法
在签名时明确指定ECDSA算法,让JJWT跳过密钥类型推断,直接使用指定算法处理:
// 替换signWith(privKey)为以下代码,根据密钥算法选择ES256/ES384/ES512 .signWith(privKey, SignatureAlgorithm.ES256)
方案3:调整JVM安全提供者顺序(不推荐)
修改JVM的安全配置文件,将SunEC提供者的顺序调整到PKCS11之前,或者禁用PKCS11提供者。例如创建自定义java.security文件,添加:
security.provider.1=sun.security.provider.Sun security.provider.2=sun.security.ec.SunEC security.provider.3=sun.security.pkcs11.SunPKCS11
然后启动JVM时指定该配置:
java -Djava.security.properties=/path/to/custom-java.security YourMainClass
注意:此方案可能影响其他依赖PKCS11的安全操作,需谨慎使用。
验证
应用上述任一方案后,重新测试JWT签名功能,确认异常不再抛出,签名成功。
内容的提问来源于stack exchange,提问作者jnasworld223

