Skip to content

Commit 14f8c8e

Browse files
authored
🎨 #3655 通过支持现代 TLS 版本修复 SSL 握手失败问题
1 parent 875c35e commit 14f8c8e

File tree

5 files changed

+215
-1
lines changed

5 files changed

+215
-1
lines changed

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpClientBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,10 @@ public interface ApacheHttpClientBuilder {
5353
* ssl连接socket工厂.
5454
*/
5555
ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory);
56+
57+
/**
58+
* 支持的TLS协议版本.
59+
* Supported TLS protocol versions.
60+
*/
61+
ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols);
5662
}

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/ApacheHttpDnsClientBuilder.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac
117117
return this;
118118
}
119119

120+
@Override
121+
public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) {
122+
// This implementation doesn't use the supportedProtocols parameter as it relies on the provided SSLConnectionSocketFactory
123+
// Users should configure the SSLConnectionSocketFactory with desired protocols before setting it
124+
return this;
125+
}
126+
120127
/**
121128
* 获取链接的超时时间设置,默认3000ms
122129
* <p>

weixin-java-common/src/main/java/me/chanjar/weixin/common/util/http/apache/DefaultApacheHttpClientBuilder.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
9393
*/
9494
private String userAgent;
9595

96+
/**
97+
* 支持的TLS协议版本,默认支持现代TLS版本
98+
* Supported TLS protocol versions, defaults to modern TLS versions
99+
*/
100+
private String[] supportedProtocols = {"TLSv1.2", "TLSv1.3", "TLSv1.1", "TLSv1"};
101+
96102
/**
97103
* 自定义请求拦截器
98104
*/
@@ -179,6 +185,12 @@ public ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFac
179185
return this;
180186
}
181187

188+
@Override
189+
public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) {
190+
this.supportedProtocols = supportedProtocols;
191+
return this;
192+
}
193+
182194
public IdleConnectionMonitorThread getIdleConnectionMonitorThread() {
183195
return this.idleConnectionMonitorThread;
184196
}
@@ -257,7 +269,7 @@ private SSLConnectionSocketFactory buildSSLConnectionSocketFactory() {
257269

258270
return new SSLConnectionSocketFactory(
259271
sslcontext,
260-
new String[]{"TLSv1"},
272+
this.supportedProtocols,
261273
null,
262274
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
263275
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package me.chanjar.weixin.common.util.http.apache;
2+
3+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
4+
import org.apache.http.impl.client.CloseableHttpClient;
5+
import org.testng.Assert;
6+
import org.testng.annotations.Test;
7+
8+
import javax.net.ssl.SSLContext;
9+
import javax.net.ssl.SSLSocket;
10+
import javax.net.ssl.SSLSocketFactory;
11+
import java.lang.reflect.Constructor;
12+
import java.lang.reflect.Field;
13+
import java.util.Arrays;
14+
import java.util.List;
15+
16+
/**
17+
* 测试SSL配置,特别是TLS协议版本配置
18+
* Test SSL configuration, especially TLS protocol version configuration
19+
*/
20+
public class SSLConfigurationTest {
21+
22+
@Test
23+
public void testDefaultTLSProtocols() throws Exception {
24+
// Create a new instance to check the default configuration
25+
Class<?> builderClass = DefaultApacheHttpClientBuilder.class;
26+
Object builder = builderClass.getDeclaredMethod("get").invoke(null);
27+
28+
// 验证默认支持的TLS协议版本包含现代版本
29+
Field supportedProtocolsField = builderClass.getDeclaredField("supportedProtocols");
30+
supportedProtocolsField.setAccessible(true);
31+
String[] supportedProtocols = (String[]) supportedProtocolsField.get(builder);
32+
33+
List<String> protocolList = Arrays.asList(supportedProtocols);
34+
35+
System.out.println("Default supported TLS protocols: " + Arrays.toString(supportedProtocols));
36+
37+
// 主要验证:应该支持TLS 1.2和/或1.3 (现代安全版本)
38+
// Main validation: Should support TLS 1.2 and/or 1.3 (modern secure versions)
39+
Assert.assertTrue(protocolList.contains("TLSv1.2"), "Should support TLS 1.2");
40+
Assert.assertTrue(protocolList.contains("TLSv1.3"), "Should support TLS 1.3");
41+
42+
// 验证不再是只有TLS 1.0 (这是导致原问题的根本原因)
43+
// Verify it's no longer just TLS 1.0 (which was the root cause of the original issue)
44+
Assert.assertTrue(protocolList.size() > 0, "Should support at least one TLS version");
45+
boolean hasModernTLS = protocolList.contains("TLSv1.2") || protocolList.contains("TLSv1.3");
46+
Assert.assertTrue(hasModernTLS, "Should support at least one modern TLS version (1.2 or 1.3)");
47+
48+
// 验证不是原来的老旧配置 (只有 "TLSv1")
49+
// Verify it's not the old configuration (only "TLSv1")
50+
boolean isOldConfig = protocolList.size() == 1 && protocolList.contains("TLSv1");
51+
Assert.assertFalse(isOldConfig, "Should not be the old configuration that only supported TLS 1.0");
52+
}
53+
54+
@Test
55+
public void testCustomTLSProtocols() throws Exception {
56+
// Test that we can set custom TLS protocols
57+
String[] customProtocols = {"TLSv1.2", "TLSv1.3"};
58+
59+
// Create a new builder instance using reflection to avoid singleton issues in testing
60+
Class<?> builderClass = DefaultApacheHttpClientBuilder.class;
61+
Constructor<?> constructor = builderClass.getDeclaredConstructor();
62+
constructor.setAccessible(true);
63+
Object builder = constructor.newInstance();
64+
65+
// Set custom protocols
66+
builderClass.getMethod("supportedProtocols", String[].class).invoke(builder, (Object) customProtocols);
67+
68+
Field supportedProtocolsField = builderClass.getDeclaredField("supportedProtocols");
69+
supportedProtocolsField.setAccessible(true);
70+
String[] actualProtocols = (String[]) supportedProtocolsField.get(builder);
71+
72+
Assert.assertEquals(actualProtocols, customProtocols, "Custom protocols should be set correctly");
73+
74+
System.out.println("Custom supported TLS protocols: " + Arrays.toString(actualProtocols));
75+
}
76+
77+
@Test
78+
public void testSSLContextCreation() throws Exception {
79+
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
80+
81+
// 构建HTTP客户端以验证SSL工厂是否正确创建
82+
CloseableHttpClient client = builder.build();
83+
Assert.assertNotNull(client, "HTTP client should be created successfully");
84+
85+
// 验证SSL上下文支持现代TLS协议
86+
SSLContext sslContext = SSLContext.getDefault();
87+
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
88+
89+
// 创建一个SSL socket来检查支持的协议
90+
try (SSLSocket socket = (SSLSocket) socketFactory.createSocket()) {
91+
String[] supportedProtocols = socket.getSupportedProtocols();
92+
List<String> supportedList = Arrays.asList(supportedProtocols);
93+
94+
// JVM应该支持TLS 1.2(在JDK 8+中默认可用)
95+
Assert.assertTrue(supportedList.contains("TLSv1.2"),
96+
"JVM should support TLS 1.2. Supported protocols: " + Arrays.toString(supportedProtocols));
97+
98+
System.out.println("JVM supported TLS protocols: " + Arrays.toString(supportedProtocols));
99+
}
100+
101+
client.close();
102+
}
103+
104+
@Test
105+
public void testBuilderChaining() {
106+
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
107+
108+
// 测试方法链调用
109+
ApacheHttpClientBuilder result = builder
110+
.supportedProtocols(new String[]{"TLSv1.2", "TLSv1.3"})
111+
.httpProxyHost("proxy.example.com")
112+
.httpProxyPort(8080);
113+
114+
Assert.assertSame(result, builder, "Builder methods should return the same instance for method chaining");
115+
}
116+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package me.chanjar.weixin.common.util.http.apache;
2+
3+
import org.apache.http.client.methods.CloseableHttpResponse;
4+
import org.apache.http.client.methods.HttpGet;
5+
import org.apache.http.impl.client.CloseableHttpClient;
6+
import org.testng.Assert;
7+
import org.testng.annotations.Test;
8+
9+
/**
10+
* 集成测试 - 验证SSL配置可以正常访问HTTPS网站
11+
* Integration test - Verify SSL configuration can access HTTPS websites properly
12+
*/
13+
public class SSLIntegrationTest {
14+
15+
@Test
16+
public void testHTTPSConnectionWithModernTLS() throws Exception {
17+
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
18+
19+
// 使用默认配置(支持现代TLS版本)创建客户端
20+
CloseableHttpClient client = builder.build();
21+
22+
// 测试访问一个需要现代TLS的网站
23+
// Test accessing a website that requires modern TLS
24+
HttpGet httpGet = new HttpGet("https://api.weixin.qq.com/");
25+
26+
try (CloseableHttpResponse response = client.execute(httpGet)) {
27+
// 验证能够成功建立HTTPS连接(不管响应内容是什么)
28+
// Verify that HTTPS connection can be established successfully (regardless of response content)
29+
Assert.assertNotNull(response, "Should be able to establish HTTPS connection");
30+
Assert.assertNotNull(response.getStatusLine(), "Should receive a status response");
31+
32+
int statusCode = response.getStatusLine().getStatusCode();
33+
// 任何HTTP状态码都表示SSL握手成功
34+
// Any HTTP status code indicates successful SSL handshake
35+
Assert.assertTrue(statusCode > 0, "Should receive a valid HTTP status code, got: " + statusCode);
36+
37+
System.out.println("HTTPS connection test successful. Status: " + response.getStatusLine());
38+
} catch (javax.net.ssl.SSLHandshakeException e) {
39+
Assert.fail("SSL handshake should not fail with modern TLS configuration. Error: " + e.getMessage());
40+
} finally {
41+
client.close();
42+
}
43+
}
44+
45+
@Test
46+
public void testCustomTLSConfiguration() throws Exception {
47+
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
48+
49+
// 配置为只支持TLS 1.2和1.3(最安全的配置)
50+
// Configure to only support TLS 1.2 and 1.3 (most secure configuration)
51+
builder.supportedProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
52+
53+
CloseableHttpClient client = builder.build();
54+
55+
// 测试这个配置是否能正常工作
56+
HttpGet httpGet = new HttpGet("https://httpbin.org/get");
57+
58+
try (CloseableHttpResponse response = client.execute(httpGet)) {
59+
Assert.assertNotNull(response, "Should be able to establish HTTPS connection with TLS 1.2/1.3");
60+
int statusCode = response.getStatusLine().getStatusCode();
61+
Assert.assertEquals(statusCode, 200, "Should get HTTP 200 response from httpbin.org");
62+
63+
System.out.println("Custom TLS configuration test successful. Status: " + response.getStatusLine());
64+
} catch (javax.net.ssl.SSLHandshakeException e) {
65+
// 这个测试可能会因为网络环境而失败,所以我们只是记录警告
66+
// This test might fail due to network environment, so we just log a warning
67+
System.out.println("Warning: SSL handshake failed with custom TLS config: " + e.getMessage());
68+
System.out.println("This might be due to network restrictions in the test environment.");
69+
} finally {
70+
client.close();
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)