升级 JDK 17 碰到的请求 https 问题
问题
升级 JDK 17 后,一个自定义请求,提示 The server selected protocol version TLS10 is not accepted
的异常,这个问题,在之前应该是碰到过的,所以有记录了对应的链接,https://cloud.tencent.com/developer/article/2127522。但这次在客户那部署,虽然调整了对应的参数,但依然还是请求失败。趁着周末重新验证了这个 TLSv1 的请求。
处理
解封 TLSv1
因为 TLSv1,TLSv1.1 有已知的安全漏洞,所以在高版本的 JDK里面默认的禁用了 TLSv1,TLSv1.1 的安全算法。通过whereis 和 ll 等命令可以定位 JAVA_HOME 目录(也可以直接尝试 echo JAVAHOME看看是否有配置对应的目录),接着定位到‘JAVA_HOME 看看是否有配置对应的目录),接着定位到`JAVAHOME看看是否有配置对应的目录),接着定位到‘JAVA_HOME/conf/security/java.security` 文件。
找到 jdk.tls.disabledAlgorithms
部分,旧内容如下:(不同JDK 版本可能略有不同)
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
去除 TLSv1, TLSv1.1,
,修改如下:
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, \DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
如果不想修改这个配置,也可以尝试的程序运行时指定参数。
java -Djdk.tls.client.protocols="TLSv1,TLSv1.1,TLSv1.2,TLSv1.3" -Djdk.tls.server.protocols="TLSv1,TLSv1.1,TLSv1.2,TLSv1.3" -jar app.jar
通常情况下,此时应该能正常使用,但发到现场后发现还是上面的错误。
模拟环境
从提示中我们可以看到,应该是对方的https 服务使用的是 TLSv1 版本的协议,于是修改自己的 nginx 服务强制指定ssl_protocols 为 TLSv1;
ssl_protocols TLSv1;ssl_ciphers HIGH:!aNULL:!MD5;
此时浏览器范围页面也是异常的。
此时通过程序测试,提示的异常虽然有些不同,但基本可以看出是同一个问题引起的了。
尝试切换 hutool 的 httputil
切换 httputil 后发现服务请求自定义证书,提示下面的错误。
经过一番查看源代码后,发现 hutool 支持设置一个全局参数HttpGlobalConfig.
setTrustAnyHost
(true)
,设置了全局参数之后发现,请求正常了。
为什么 httputils 不行呢?
一开始以为是创建的 SSLContext 不同,更换成 hutool 工具创建的 SSLContext SSLContextUtil.createTrustAnySSLContext
依然还是不行。于是对比了下 hutool 和 solon 的是请求默认的类库不同,hutool 默认使用的是httpclient4,solon默认使用的是okhttp。当把 hutool 的默认请求切换到 okhttp 时,虽然一样也设置了*setTrustAnyHost
*(true)
,但请求一样是报错的。
接着跟踪到 hutool 中 httpclient4 里面的一个关键设置。
private static SSLConnectionSocketFactory buildSocketFactory(SSLInfo sslInfo) {return null == sslInfo ? SSLConnectionSocketFactory.getSocketFactory() : new SSLConnectionSocketFactory(sslInfo.getSslContext(), sslInfo.getProtocols(), (String[])null, sslInfo.getHostnameVerifier());
}
此时可以比较明确的确定是连接客户也需要设置的问题,于是找到 OkHttpClient 创建的位置,增加设置connectionSpecs
:
solon 作者说按上述设置会导致 http 无法访问,同时还发现 okhttp 还提供了一些常量,换成如下写法兼容性更好。
connectionSpecs(Arrays.asList(ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT))
临时处理
扩展 OkHttpUtilsFactory
public class OkHttpUtilsFactoryExt extends OkHttpUtilsFactory {static final Logger log = LoggerFactory.getLogger(OkHttpUtilsFactory.class);private static final OkHttpUtilsFactory instance = new OkHttpUtilsFactory();private static final OkHttpDispatcher dispatcher = new OkHttpDispatcher();private final OkHttpClient defaultClient = createHttpClient(null, null);private static OkHttpClient createHttpClient(Proxy proxy, HttpSslSupplier sslProvider) {if (sslProvider == null) {sslProvider = HttpSslSupplierDefault.getInstance();}OkHttpClient.Builder builder =(new OkHttpClient.Builder()).connectTimeout(10L, TimeUnit.SECONDS).writeTimeout(60L, TimeUnit.SECONDS).readTimeout(60L, TimeUnit.SECONDS).dispatcher(dispatcher.getDispatcher()).addInterceptor(OkHttpInterceptor.instance).sslSocketFactory(sslProvider.getSocketFactory(), sslProvider.getX509TrustManager()).connectionSpecs(Arrays.asList(ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT)).hostnameVerifier(sslProvider.getHostnameVerifier());if (proxy != null) {builder.proxy(proxy);}return builder.build();}@Overrideprotected OkHttpClient getClient(Proxy proxy, HttpSslSupplier sslProvider) {return createHttpClient(proxy, sslProvider);}
}
扩展 HttpSslSupplier
@Component
public class ExtensionHttp extends HttpSslSupplierDefaultimplements HttpExtension, HttpSslSupplier {// for HttpSslSupplierprivate SSLContext sslContext;private String[] protocols;// for HttpExtension@Overridepublic void onInit(HttpUtils httpUtils, String url) {httpUtils.ssl(this);}@Overridepublic SSLContext getSslContext() {return SSLContextUtil.createTrustAnySSLContext();}
}
使用
如果指定了 HttpExtension的扩展为 Component,默认情况是会自动注册扩展的,测试时可能需要自己通过addExtension 的方式添加扩展。
HttpConfiguration.setFactory(new OkHttpUtilsFactoryExt());
HttpConfiguration.addExtension(new ExtensionHttp());
正式处理
等待 solon 3.5.0 版本的发布,之后切换 SSLContextUtil.
createTrustAnySSLContext
()
,替换默认的SSLContext。