🎨 #3655 通过支持现代 TLS 版本修复 SSL 握手失败问题
Some checks failed
Publish to Maven Central / build-and-publish (push) Has been cancelled

This commit is contained in:
Copilot 2025-07-25 11:41:06 +08:00 committed by GitHub
parent 875c35e745
commit 14f8c8ebc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 215 additions and 1 deletions

View File

@ -53,4 +53,10 @@ public interface ApacheHttpClientBuilder {
* ssl连接socket工厂.
*/
ApacheHttpClientBuilder sslConnectionSocketFactory(SSLConnectionSocketFactory sslConnectionSocketFactory);
/**
* 支持的TLS协议版本.
* Supported TLS protocol versions.
*/
ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols);
}

View File

@ -117,6 +117,13 @@ public class ApacheHttpDnsClientBuilder implements ApacheHttpClientBuilder {
return this;
}
@Override
public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) {
// This implementation doesn't use the supportedProtocols parameter as it relies on the provided SSLConnectionSocketFactory
// Users should configure the SSLConnectionSocketFactory with desired protocols before setting it
return this;
}
/**
* 获取链接的超时时间设置,默认3000ms
* <p>

View File

@ -93,6 +93,12 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
*/
private String userAgent;
/**
* 支持的TLS协议版本默认支持现代TLS版本
* Supported TLS protocol versions, defaults to modern TLS versions
*/
private String[] supportedProtocols = {"TLSv1.2", "TLSv1.3", "TLSv1.1", "TLSv1"};
/**
* 自定义请求拦截器
*/
@ -179,6 +185,12 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
return this;
}
@Override
public ApacheHttpClientBuilder supportedProtocols(String[] supportedProtocols) {
this.supportedProtocols = supportedProtocols;
return this;
}
public IdleConnectionMonitorThread getIdleConnectionMonitorThread() {
return this.idleConnectionMonitorThread;
}
@ -257,7 +269,7 @@ public class DefaultApacheHttpClientBuilder implements ApacheHttpClientBuilder {
return new SSLConnectionSocketFactory(
sslcontext,
new String[]{"TLSv1"},
this.supportedProtocols,
null,
SSLConnectionSocketFactory.getDefaultHostnameVerifier());
} catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {

View File

@ -0,0 +1,116 @@
package me.chanjar.weixin.common.util.http.apache;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.CloseableHttpClient;
import org.testng.Assert;
import org.testng.annotations.Test;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
/**
* 测试SSL配置特别是TLS协议版本配置
* Test SSL configuration, especially TLS protocol version configuration
*/
public class SSLConfigurationTest {
@Test
public void testDefaultTLSProtocols() throws Exception {
// Create a new instance to check the default configuration
Class<?> builderClass = DefaultApacheHttpClientBuilder.class;
Object builder = builderClass.getDeclaredMethod("get").invoke(null);
// 验证默认支持的TLS协议版本包含现代版本
Field supportedProtocolsField = builderClass.getDeclaredField("supportedProtocols");
supportedProtocolsField.setAccessible(true);
String[] supportedProtocols = (String[]) supportedProtocolsField.get(builder);
List<String> protocolList = Arrays.asList(supportedProtocols);
System.out.println("Default supported TLS protocols: " + Arrays.toString(supportedProtocols));
// 主要验证应该支持TLS 1.2和/或1.3 (现代安全版本)
// Main validation: Should support TLS 1.2 and/or 1.3 (modern secure versions)
Assert.assertTrue(protocolList.contains("TLSv1.2"), "Should support TLS 1.2");
Assert.assertTrue(protocolList.contains("TLSv1.3"), "Should support TLS 1.3");
// 验证不再是只有TLS 1.0 (这是导致原问题的根本原因)
// Verify it's no longer just TLS 1.0 (which was the root cause of the original issue)
Assert.assertTrue(protocolList.size() > 0, "Should support at least one TLS version");
boolean hasModernTLS = protocolList.contains("TLSv1.2") || protocolList.contains("TLSv1.3");
Assert.assertTrue(hasModernTLS, "Should support at least one modern TLS version (1.2 or 1.3)");
// 验证不是原来的老旧配置 (只有 "TLSv1")
// Verify it's not the old configuration (only "TLSv1")
boolean isOldConfig = protocolList.size() == 1 && protocolList.contains("TLSv1");
Assert.assertFalse(isOldConfig, "Should not be the old configuration that only supported TLS 1.0");
}
@Test
public void testCustomTLSProtocols() throws Exception {
// Test that we can set custom TLS protocols
String[] customProtocols = {"TLSv1.2", "TLSv1.3"};
// Create a new builder instance using reflection to avoid singleton issues in testing
Class<?> builderClass = DefaultApacheHttpClientBuilder.class;
Constructor<?> constructor = builderClass.getDeclaredConstructor();
constructor.setAccessible(true);
Object builder = constructor.newInstance();
// Set custom protocols
builderClass.getMethod("supportedProtocols", String[].class).invoke(builder, (Object) customProtocols);
Field supportedProtocolsField = builderClass.getDeclaredField("supportedProtocols");
supportedProtocolsField.setAccessible(true);
String[] actualProtocols = (String[]) supportedProtocolsField.get(builder);
Assert.assertEquals(actualProtocols, customProtocols, "Custom protocols should be set correctly");
System.out.println("Custom supported TLS protocols: " + Arrays.toString(actualProtocols));
}
@Test
public void testSSLContextCreation() throws Exception {
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
// 构建HTTP客户端以验证SSL工厂是否正确创建
CloseableHttpClient client = builder.build();
Assert.assertNotNull(client, "HTTP client should be created successfully");
// 验证SSL上下文支持现代TLS协议
SSLContext sslContext = SSLContext.getDefault();
SSLSocketFactory socketFactory = sslContext.getSocketFactory();
// 创建一个SSL socket来检查支持的协议
try (SSLSocket socket = (SSLSocket) socketFactory.createSocket()) {
String[] supportedProtocols = socket.getSupportedProtocols();
List<String> supportedList = Arrays.asList(supportedProtocols);
// JVM应该支持TLS 1.2在JDK 8+中默认可用
Assert.assertTrue(supportedList.contains("TLSv1.2"),
"JVM should support TLS 1.2. Supported protocols: " + Arrays.toString(supportedProtocols));
System.out.println("JVM supported TLS protocols: " + Arrays.toString(supportedProtocols));
}
client.close();
}
@Test
public void testBuilderChaining() {
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
// 测试方法链调用
ApacheHttpClientBuilder result = builder
.supportedProtocols(new String[]{"TLSv1.2", "TLSv1.3"})
.httpProxyHost("proxy.example.com")
.httpProxyPort(8080);
Assert.assertSame(result, builder, "Builder methods should return the same instance for method chaining");
}
}

View File

@ -0,0 +1,73 @@
package me.chanjar.weixin.common.util.http.apache;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.testng.Assert;
import org.testng.annotations.Test;
/**
* 集成测试 - 验证SSL配置可以正常访问HTTPS网站
* Integration test - Verify SSL configuration can access HTTPS websites properly
*/
public class SSLIntegrationTest {
@Test
public void testHTTPSConnectionWithModernTLS() throws Exception {
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
// 使用默认配置支持现代TLS版本创建客户端
CloseableHttpClient client = builder.build();
// 测试访问一个需要现代TLS的网站
// Test accessing a website that requires modern TLS
HttpGet httpGet = new HttpGet("https://api.weixin.qq.com/");
try (CloseableHttpResponse response = client.execute(httpGet)) {
// 验证能够成功建立HTTPS连接不管响应内容是什么
// Verify that HTTPS connection can be established successfully (regardless of response content)
Assert.assertNotNull(response, "Should be able to establish HTTPS connection");
Assert.assertNotNull(response.getStatusLine(), "Should receive a status response");
int statusCode = response.getStatusLine().getStatusCode();
// 任何HTTP状态码都表示SSL握手成功
// Any HTTP status code indicates successful SSL handshake
Assert.assertTrue(statusCode > 0, "Should receive a valid HTTP status code, got: " + statusCode);
System.out.println("HTTPS connection test successful. Status: " + response.getStatusLine());
} catch (javax.net.ssl.SSLHandshakeException e) {
Assert.fail("SSL handshake should not fail with modern TLS configuration. Error: " + e.getMessage());
} finally {
client.close();
}
}
@Test
public void testCustomTLSConfiguration() throws Exception {
DefaultApacheHttpClientBuilder builder = DefaultApacheHttpClientBuilder.get();
// 配置为只支持TLS 1.2和1.3最安全的配置
// Configure to only support TLS 1.2 and 1.3 (most secure configuration)
builder.supportedProtocols(new String[]{"TLSv1.2", "TLSv1.3"});
CloseableHttpClient client = builder.build();
// 测试这个配置是否能正常工作
HttpGet httpGet = new HttpGet("https://httpbin.org/get");
try (CloseableHttpResponse response = client.execute(httpGet)) {
Assert.assertNotNull(response, "Should be able to establish HTTPS connection with TLS 1.2/1.3");
int statusCode = response.getStatusLine().getStatusCode();
Assert.assertEquals(statusCode, 200, "Should get HTTP 200 response from httpbin.org");
System.out.println("Custom TLS configuration test successful. Status: " + response.getStatusLine());
} catch (javax.net.ssl.SSLHandshakeException e) {
// 这个测试可能会因为网络环境而失败所以我们只是记录警告
// This test might fail due to network environment, so we just log a warning
System.out.println("Warning: SSL handshake failed with custom TLS config: " + e.getMessage());
System.out.println("This might be due to network restrictions in the test environment.");
} finally {
client.close();
}
}
}