CrazyAirhead

疯狂的傻瓜,傻瓜也疯狂——傻方能执著,疯狂才专注!

0%

升级 JDK 17 碰到的请求 https 问题

问题

升级 JDK 17 后,一个自定义请求,提示 The server selected protocol version TLS10 is not accepted的异常,这个问题,在之前应该是碰到过的,所以有记录了对应的链接,https://cloud.tencent.com/developer/article/2127522。但这次在客户那部署,虽然调整了对应的参数,但依然还是请求失败。趁着周末重新验证了这个 TLSv1 的请求。

img

处理

解封 TLSv1

因为 TLSv1,TLSv1.1 有已知的安全漏洞,所以在高版本的 JDK里面默认的禁用了 TLSv1,TLSv1.1 的安全算法。通过whereis 和 ll 等命令可以定位 JAVA_HOME 目录(也可以直接尝试 echo $JAVA_HOME 看看是否有配置对应的目录),接着定位到$JAVA_HOME/conf/security/java.security 文件。

找到 jdk.tls.disabledAlgorithms部分,旧内容如下:(不同JDK 版本可能略有不同)

1
2
jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA, \
DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL

去除 TLSv1, TLSv1.1, ,修改如下:

1
2
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, \
DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL

如果不想修改这个配置,也可以尝试的程序运行时指定参数。

1
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;

1
2
ssl_protocols TLSv1;
ssl_ciphers HIGH:!aNULL:!MD5;

此时浏览器范围页面也是异常的。

img

此时通过程序测试,提示的异常虽然有些不同,但基本可以看出是同一个问题引起的了。

img

尝试切换 hutool 的 httputil

切换 httputil 后发现服务请求自定义证书,提示下面的错误。

img

经过一番查看源代码后,发现 hutool 支持设置一个全局参数HttpGlobalConfig.setTrustAnyHost(true),设置了全局参数之后发现,请求正常了。

img

为什么 httputils 不行呢?

一开始以为是创建的 SSLContext 不同,更换成 hutool 工具创建的 SSLContext SSLContextUtil.createTrustAnySSLContext 依然还是不行。于是对比了下 hutool 和 solon 的是请求默认的类库不同,hutool 默认使用的是httpclient4,solon默认使用的是okhttp。当把 hutool 的默认请求切换到 okhttp 时,虽然一样也设置了*setTrustAnyHost*(true),但请求一样是报错的。

接着跟踪到 hutool 中 httpclient4 里面的一个关键设置。

1
2
3
private static SSLConnectionSocketFactory buildSocketFactory(SSLInfo sslInfo) {
return null == sslInfo ? SSLConnectionSocketFactory.getSocketFactory() : new SSLConnectionSocketFactory(sslInfo.getSslContext(), sslInfo.getProtocols(), (String[])null, sslInfo.getHostnameVerifier());
}

此时可以比较明确的确定是连接客户也需要设置的问题,于是找到 OkHttpClient 创建的位置,增加设置connectionSpecs

img

solon 作者说按上述设置会导致 http 无法访问,同时还发现 okhttp 还提供了一些常量,换成如下写法兼容性更好。

1
connectionSpecs(Arrays.asList(ConnectionSpec.COMPATIBLE_TLS, ConnectionSpec.CLEARTEXT))

临时处理

扩展 OkHttpUtilsFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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();
}

@Override
protected OkHttpClient getClient(Proxy proxy, HttpSslSupplier sslProvider) {
return createHttpClient(proxy, sslProvider);
}
}

扩展 HttpSslSupplier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
public class ExtensionHttp extends HttpSslSupplierDefault
implements HttpExtension, HttpSslSupplier {
// for HttpSslSupplier
private SSLContext sslContext;
private String[] protocols;

// for HttpExtension
@Override
public void onInit(HttpUtils httpUtils, String url) {
httpUtils.ssl(this);
}

@Override
public SSLContext getSslContext() {
return SSLContextUtil.createTrustAnySSLContext();
}
}

使用

如果指定了 HttpExtension的扩展为 Component,默认情况是会自动注册扩展的,测试时可能需要自己通过addExtension 的方式添加扩展。

1
2
HttpConfiguration.setFactory(new OkHttpUtilsFactoryExt());
HttpConfiguration.addExtension(new ExtensionHttp());

正式处理

等待 solon 3.5.0 版本的发布,之后切换 SSLContextUtil.createTrustAnySSLContext(),替换默认的SSLContext。

欢迎关注我的其它发布渠道