当服务器使用服务器名称指示 (SNI) 扩展时,如何使用 HttpClient 4.5.2 和 JDK 1.8 获取 HTTPS Web 资源?

Posted

技术标签:

【中文标题】当服务器使用服务器名称指示 (SNI) 扩展时,如何使用 HttpClient 4.5.2 和 JDK 1.8 获取 HTTPS Web 资源?【英文标题】:How to fetch HTTPS web resurce with HttpClient 4.5.2 & JDK 1.8 when server uses Server Name Indication (SNI) extension? 【发布时间】:2016-08-06 18:20:17 【问题描述】:

我无法使用 HttpClient 从 SNI 站点获取资源。我要获取的 URL 是:https://dabar.srce.hr/、https://www.lutrija.hr/cms/splash。

我收到此错误: javax.net.ssl.SSLHandshakeException:收到致命警报:handshake_failure

据我了解,它应该像这样开箱即用地工作(它适用于非 sni https 网站):

url = "https://dabar.srce.hr/";
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() 
    // trust all certificates
    public boolean isTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException 
        return true;
    
).build();
SSLConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(sslSF).build();
HttpGet httpGet = new HttpGet(url);
CloseableHttpResponse response = httpclient.execute(httpGet);
System.out.println(response.getStatusLine().toString());
HttpEntity entity = response.getEntity();
System.out.println(EntityUtils.toString(entity));

我尝试显式启用 SNIExtension:

System.setProperty("jsse.enableSNIExtension", "true");                        

我尝试覆盖 SSLConnectionSocketFactory:

SSLConnectionSocketFactory sslSF = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE) 
    String targetHost = "";
    @Override
    public Socket createLayeredSocket(Socket socket, String target, int port, HttpContext context)
                throws IOException 
        this.targetHost = target;
        return super.createLayeredSocket(socket, target, port, context);
    
    @Override
    protected void prepareSocket(SSLSocket socket) throws IOException 
        try 
            PropertyUtils.setProperty(socket, "host", this.targetHost);
         catch (Exception ex) 
        
        super.prepareSocket(socket);
    
    @Override
    public Socket connectSocket(int connectTimeout, Socket socket, HttpHost host, InetSocketAddress remoteAddress, InetSocketAddress localAddress, HttpContext context) 
        throws IOException 
        if (socket instanceof SSLSocket) 
            try 
                PropertyUtils.setProperty(socket, "host", host.getHostName());
             catch (NoSuchMethodException e) 
             catch (IllegalAccessException e) 
             catch (InvocationTargetException e) 
        
    
    return super.connectSocket(connectTimeout, socket, host, remoteAddress,
                    localAddress, context);
    
;

我错过了什么?

【问题讨论】:

【参考方案1】:

我遇到了同样的问题。这个自定义 SSLConnectionSocketFactory 为我修复了它:

公共类 TrustAllSslSocketFactory 扩展 SSLConnectionSocketFactory static final HostnameVerifier ALL_HOSTNAME_VERIFIER = (String hostname, SSLSession session) -> true; 公共 TrustAllSslSocketFactory(SSLContext sslContext) 超级(sslContext,ALL_HOSTNAME_VERIFIER); @覆盖 protected void prepareSocket(SSLSocket socket) 抛出 IOException 字符串主机名 = socket.getInetAddress().getHostName(); SNIHostName sniHostName = new SNIHostName(hostName); 列出 serverNames = new ArrayList(1); serverNames.add(sniHostName); SSLParameters sslParameters = socket.getSSLParameters(); sslParameters.setServerNames(serverNames); socket.setSSLParameters(sslParameters);

见http://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#SNIExamples

【讨论】:

重写的 prepareSocket() 方法毫无意义,因为始终返回 true 的 HostnameVerifier 将允许任何主机名。

以上是关于当服务器使用服务器名称指示 (SNI) 扩展时,如何使用 HttpClient 4.5.2 和 JDK 1.8 获取 HTTPS Web 资源?的主要内容,如果未能解决你的问题,请参考以下文章

如何实现服务器名称指示 (SNI)

从 TLS 客户端 hello 中提取服务器名称指示 (SNI)

SNI协议分析

使用 SNI 通过 HTTPS 提供服务时出现 CloudFront 错误

IIS 8.5 上的 SNI 和 SSL

nginx和SNI那些事