文章目录
1. JVM 类加载异常
1. 出现问题
- 使用 HTTP 协议对接第三方服务,本地测试可以正常成功,测试环境连接第三方失败,项目使用的 maven 是 httpclient-4.5.1
- 抛出的异常为
Javax.net.ssl.SSLException: Received fatal alert: protocol_version
Javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
2. 解决过程
1. JDK 7 版本过老
- 通过异常得知 SSL 加密协议版本问题,查询第三方文档,得知仅支持 HTTPS 使用 TLS 1.2 加密方式
- 排查发现本地使用 JDK 1.8.0_221,线上使用的是 JDK 7,通过搜索可知 JDK 7 调用第三方 HTTPS 接口、默认使用的是 TLS1 加密方式,而非 TLS 1.2 加密方式,JDK 1.8 默认 TLS 1.2
- 细看 HTTP 调用工具类使用了 SSLContext.getInstance(“TLSv1.2”) 强制改变加密方式,但是依然异常,加
System.setProperty("javax.net.debug", "all");
打印出网络调试日志,发现调用异常且还是使用的 TLS 1
2. JDK 8 小版本无关
- 改动业务到使用了 jdk1.8.0_131 的微服务上,仍有异常、但异常改变为
Caused by: Java.security.NoSuchAlgorithmException: Cannot find any provider supporting DESede/CBC/PKCS5Padding
- 仔细对比本地使用的是 jdk1.8.0_321,通过 Oracle 官方文档可知 8u211 相对 8u202 有较大更新,因此测试环境尝试升级 jdk1.8.0_131,还是相同异常,发现与小版本无关
3. 缺少 SunEC version 1.8 加密包
- 根据异常,本地 debug 查到 Javax.crypto.Cipher 类,通过在线 JDK8 文档查到加密类型是 Java 平台均支持的(意思和本地使用 Mac,测试环境使用 Linux 无关),但确实在加密时无法获取 DESede/CBC/PKCS5Padding ,同时也尝试其他加密方式异常相同
- 后续通过搜索查到添加:
System.out.println(Arrays.toString(Security.getProviders()));
发现 Linux 环境缺少了 SunEC version 1.8 加密包 - 本地 Mac:[SUN version 1.8, SunRsaSign version 1.8, SunEC version 1.8, SunJSSE version 1.8, SunJCE version 1.8, SunJGSS version 1.8, SunSASL version 1.8, XMLDSig version 1.8, SunPCSC version 1.8, Apple version 1.8]
- 测试 Linux:[SUN version 1.8, SunRsaSign version 1.8, SunJSSE version 1.8, SunJGSS version 1.8, SunSASL version 1.8, XMLDSig version 1.8, SunPCSC version 1.8]
4. 强制添加加密包无效
- 然后测试环境强制添加:
Security.addProvider(new com.sun.crypto.provider.SunJCE());
- 抛出异常,还是找不到 jar 包
com.sun.crypto.provider.SunJCE() 2022-04-19 17:16:09,144 \[// - - -\] ERROR com.alibaba.dubbo.rpc.filter.ExceptionFilter - \[DUBBO\] Got unchecked and undeclared exception which called by xxx.xxx.xxx.xxx. service: com.xxx.xxx, method: xxx, exception: Java.lang.NoClassDefFoundError: com/sun/crypto/provider/SunJCE, dubbo version: x.x.x, current host: xxx.xxx.xxx.xxx Java.lang.NoClassDefFoundError: com/sun/crypto/provider/SunJCE
- Linux 下载 JDK 查对应包,是存在的
\$JAVA_HOME/jre/lib The 4 jar packages in the / directory are as follows: jce.jar security/US_export_policy.jar security/local_policy.jar ext/sunjce_provider.jar
5. 找到 JVM 类加载问题
- 随后想到,问题就是 JVM 类加载时无法加载到这个类,查到这个类具体在 JRE 的:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar
- 隐约想起《码出高效》上面写过,Extension ClassLoader 加载了 jre/lib/ext/*.jar
- 正好对上了,那就在项目中搜索这几个关键词,发现启动脚本使用了:-DJava.ext.dirs=$DEPLOY_DIR/lib
- 搜索发现,这个命令会覆盖 Java 本身的 ext 包的加载,而去加载指定 pom 文件的 jar 包,找到 jar 目录发现其他加密包均存在,果然少了 SunJCE 加密包
- 因此使用此加解密算法就会抛出异常
3. 解决方案
- 有三种解决方案
- 将 ext 相关 jar 包复制到当前 lib 目录,重启加载此 jar 包
- -DJava.ext.dirs 配置多个目录,重启加载此 jar 包
- 将 jar 包打包发布到 Nexus Maven 私服,使用 Maven 依赖即可