概述

当使用httpclinet发起https请求时报如下错误:
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:174)
    at com.sun.net.ssl.internal.ssl.Alerts.getSSLException(Alerts.java:136)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.recvAlert(SSLSocketImpl.java:1657)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:932)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1096)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1123)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1107)
    at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:261)
    at org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:118)
    at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:314)
    at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:357)
    at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:218)
    at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:194)
    at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:85)
    at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:108)
    at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:186)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
    at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)

未解决参照url

访问https,抛出的异常javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
使用HttpClient发送HTTPS请求以及配置Tomcat支持SSL

分析过程

背景交待

由于证书是字自签自发,并且加固过,相信很多人会问为什么加固, 因为你如果不加固的话https将在ff中无法访问,错误如下:
记录一次解决httpcline请求https报handshake_failure错误

解决方案参照Tomcat6+JDK6如何加固,解决Logjam attack

解决过程分析

不加固是否可以直接访问

经测试 不加固的情况访问没有问题

加固后不能访问原因分析

因为加固主要是指定了protocols和ciphers,所以请求时是否也可以指定protocols和ciphers,查阅官方文档发现如下信息
记录一次解决httpcline请求https报handshake_failure错误
通过在httpclient请求之前设置protocols和ciphers,代码如下:

        System.setProperty("https.protocols", "与server.xml中的protocols一致");
        System.setProperty("https.cipherSuites", "与server.xml中的ciphers一致");

重新发起请求,发现还是报错

分析设置是否生效

通过debug httpclinet下HttpClientBuilder类的源代码发现如下
记录一次解决httpcline请求https报handshake_failure错误
则将代码增加如下粗体:
HttpClients.custom().useSystemProperties().setDefaultRequestConfig(defaultRequestConfig).setSslcontext(sslcontext).build();
重新发起请求,发现还是报错

查询本地支持的协议及算法

代码如下:

public class HttpsTest {
    public static void main(String[] args) {
        SSLContext sc;
        try {
            sc = SSLContext.getInstance("TLS");
            // 实现一个X509TrustManager接口,用于绕过验证,不用修改里面的方法
            X509TrustManager trustManager = new X509TrustManager() {
                @Override
                public void checkClientTrusted(
                        java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                        String paramString) throws CertificateException {
                }

                @Override
                public void checkServerTrusted(
                        java.security.cert.X509Certificate[] paramArrayOfX509Certificate,
                        String paramString) throws CertificateException {
                }

                @Override
                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                    return null;
                }
            };

            sc.init(null, new TrustManager[] { trustManager }, null);
            System.out.println("缺省安全套接字使用的协议: " + sc.getProtocol());  
            // 获取SSLContext实例相关的SSLEngine  
            SSLEngine en = sc.createSSLEngine();  
            System.out  
                    .println("支持的协议: " + Arrays.asList(en.getSupportedProtocols()));  
            System.out.println("启用的协议: " + Arrays.asList(en.getEnabledProtocols()));  
            System.out.println("支持的加密套件: "  
                    + Arrays.asList(en.getSupportedCipherSuites()));  
            System.out.println("启用的加密套件: "  
                    + Arrays.asList(en.getEnabledCipherSuites())); 
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}
然后在httpclient请求之前设置protocols和ciphers,
    System.setProperty("https.protocols", "其值为服务器和本地相同的");
    System.setProperty("https.cipherSuites", "其值为服务器和本地相同的");

重新发起请求,请求成功。

版本说明

httpclinet:4.3.1
jdk:1.6
tomcat:6

httpclient发起请求代码

访问 https://gitee.com/die/help_common.git中的httpclinet进行下载

参考文章

HttpClient如何指定CipherSuites