You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache HttpClient5.1+Java11 TLS握手缺失server_name扩展致连接重置

问题背景
  • 技术栈:Apache HttpClient 5.1 + Java 11,对接开启双向SSL认证的服务端API
  • 故障现象:加载TrustMaterial构建SSL上下文、初始化CloseableHttpClient发起GET请求时,持续抛出Connection Reset错误
  • 初步定位:通过配置System.setProperty("javax.net.debug","ssl,handshake")开启SSL握手调试日志,发现TLS握手发出的ClientHello消息extensions字段缺失*server_name(SNI)*扩展,判断为连接失败核心原因
问题复现代码
public class ClientCustomSSL {
    public final static void main(final String[] args) throws Exception {
        System.setProperty("javax.net.debug","ssl,handshake");

        File file = new File("C:\\Mycerts.jks");
        final SSLContext sslcontext = SSLContexts.custom().loadTrustMaterial(file,"passcode".toCharArray()).build();
        final SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
                .setSslContext(sslcontext).build();
        final HttpClientConnectionManager cm = PoolingHttpClientConnectionManagerBuilder.create()
                .setSSLSocketFactory(sslSocketFactory)
                .build();
        try (CloseableHttpClient httpclient = HttpClients.custom()
             .setConnectionManager(cm).build()) {

            final HttpGet httpget = new HttpGet("https://mysite-s123/");
            final HttpClientContext clientContext = HttpClientContext.create();
            try (CloseableHttpResponse response = httpclient.execute(httpget, clientContext)) {
                System.out.println(response.getCode() + " " + response.getReasonPhrase());
            }
        }
    } 
}
关键错误日志

日志核心特征:

  • 明确提示SNI扩展生成失败:Unable to indicate server name、Ignore, context unavailable extension: server name
  • 最终抛出连接重置异常:java.net.SocketException: Connection reset
javax.net.ssl|WARNING|01|main|2022-07-08 14:39:57.291 EDT|ServerNameExtension.java: 261 | Unable to indicate server name
javax.net.ssl|DEBUG|01|main|2022-07-08 14:39:57.291 EDT|SSLExtensions.java: 260|Ignore, context unavailable extension: server name
javax.net.ssl|WARNING| 01|main|2022-07-08 14:39:57.293 EDT|SignatureScheme.java:295|Signature algorithm, ed25519, is not supported by the underlying providers
javax.net.ssl|WARNING|01|main|2022-07-08 14:39:57.293 EDT|SignatureScheme.java: 295|Signature algorithm, ed448, is not supported by the underlying providers
javax.net.ssl|INFO|01|main|2022-07-08 14:39:57.297 EDT|AlpnExtension.java:178|No available application protocols
javax.net.ssl|DEBUG|02|main|2022-07-08 14:39:57.297 EDT|SSLExtensions.java: 260| Ignore, context unavailable extension: application layer protocol negotiation
javax.net.ssl|DEBUG|01|main|2022-07-08 14:39:57.297 EDT|SSLExtensions.java:260|Ignore,context unavailable extension: renegotiation_info
javax.net.ssl|DEBUG|01|main|2022-07-08 14:39:57.299 EDT|ClientHello.java: 642 Produced ClientHello handshake message
"ClientHello":{
"client version":"TLSv1.2",
"random":"E1 A8 CF DD 06 87 B9 FD 6C EB FE C6 BC E3 C4 7F EE DC 22 B3 B7 AC 40 0A D0 53 7F 81 51 5D 78 D9",
"session id":"E1 A8 CF DD 06 87 B9 FD 6C EB FE C6 BC E3 C4 7F EE DC 22 B3 B7 AC 40 0A D0 78 D9", 
"cipher suites":"[TLS_AES_256_GCM_SHA384(0x1302),TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384(0xC02C), TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256(0xC02B), TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xCCA9)...]",
"compression methods": "00",
"extensions": [
    "status request (5)":{
    "certificate status type": ocsp
    "OCSP status request": {
        "responder id": <empty>
        "request extensions":{
        <empty>
        }
    }
},
"supported_groups (10)": {
    "versions": [x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096, ffdhe6144, ffdhe8192]
},
"ec point formats (11)": {
    "formats": [uncompressed]
},
"signature algorithms (13)": {
        "signature schemes": [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256, dsa_sha1]
},
    "signature algorithms_cert (50)": {
        "signature schemes" : [ecdsa_secp256r1_sha256, ecdsa_secp384r1_sha384, ecdsa_secp521r1_sha512, rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512, rsa_pss_pss_sha256,dsa_sha1]
        },
        "status request_v2 (17)": {
        "cert status request":{
        "certificate status type": ocsp multi
}
]
}
javax.net.ssl|WARNING|01|main|2022-07-07 09:26:16.233 EDT| SSLSocketImpl.java: 1648| handling exception ("throwable" : {
    java.net.SocketException: Connection reset
        at java.base/java.net.SocketInputStream.read(SocketInputStream.java:186)
        at java.base/java.net.SocketInputStream.read (SocketInputStream.java: 140)
        at java.base/sun.security.ssl.SSLSocketInputRecord.read(SSLSocketInputRecord.java:478)
        at java.base/sun.security.ssl.SSLSocketInputRecord.readHeader(SSLSocketInputRecord.java:472)
        at java.base/sun.security.ssl.SSLSocketInputRecord.decode(SSLSocketInputRecord.java:160)
        at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:111)
        at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1501)
        at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1411)
        at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:451)
        at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:422)
        at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:436)
        at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:384)
        at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:142)
        at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376)
        at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393)
        at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
        at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186)
        at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89)
        at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
        at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83)
        at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108)
        at ClientCustomSSL.main(ClientCustomSSL.java:62)}
排查步骤与修复方案

SNI扩展默认不生效,按以下顺序排查:

  1. 检查JVM全局配置
    确认没有配置启动参数-Djsse.enableSNIExtension=false,该参数默认值为true,如果被手动改为false会全局禁用SNI。
  2. 补全SSL连接工厂配置
    当前自定义SSLConnectionSocketFactory时仅传入了SSL上下文,缺失主机名验证与TLS细节配置,导致JSSE层拿不到目标主机名,无法生成SNI扩展。修改连接工厂构建代码:
    import org.apache.http.ssl.DefaultTlsDetailsFactory;
    import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
    
    final SSLConnectionSocketFactory sslSocketFactory = SSLConnectionSocketFactoryBuilder.create()
            .setSslContext(sslcontext)
            // 显式配置默认主机名验证器
            .setHostnameVerifier(SSLConnectionSocketFactory.getDefaultHostnameVerifier())
            // 配置TLS细节工厂,确保握手阶段能获取到目标主机名
            .setTlsDetailsFactory(new DefaultTlsDetailsFactory())
            .build();
    
  3. 短域名/IP直连场景显式指定SNI主机名
    如果调用的地址是内部短域名(比如示例里的mysite-s123)或者IP直连,Java默认不会为非完全限定域名生成SNI扩展,需要在请求上下文里显式指定目标虚拟主机:
    final HttpClientContext clientContext = HttpClientContext.create();
    // 替换为服务端实际配置证书对应的完整域名
    clientContext.setTargetHost(new HttpHost("mysite-s123.your-company.com", 443, "https"));
    
  4. 验证修复结果
    重启应用发起请求,观察SSL调试日志,如果ClientHello的extensions列表中出现server_name字段,且不再抛出Connection Reset错误,说明配置生效。

内容的提问来源于stack exchange,提问作者Knight

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 00:21:49