HttpClient:每个请求都进行SSL握手

9

我使用静态的HttpClient,但是在https上运行非常缓慢。我已经添加了-Djavax.net.debug=ssl,并发现每个https请求都会重新开始握手。看起来它无法重用旧会话,但我找不到原因。

9007199254743735, setSoTimeout(0) called
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
9007199254743735, setSoTimeout(0) called
%% No cached client session
*** ClientHello, SSLv3
...
%% Didn't cache non-resumable client session: [Session-1, SSL_RSA_WITH_RC4_128_MD5]
...
Is initial handshake: true

顺便提一下,在这个主机上我曾经遇到另一个问题:“Received fatal alert: bad_record_mac”,通过仅允许SSLv3解决了这个问题。

更新1:HttpClient初始化代码

    final SSLContext sslCtx;
    sslCtx = SSLContext.getInstance("SSL");
    sslCtx.init(null, new TrustManager[]{new X509TrustManager() {
            @Override
            public void checkClientTrusted(X509Certificate[] cert,
                    String authType) {
            }

            @Override
            public void checkServerTrusted(X509Certificate[] cert,
                    String authType) {
            }

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }
        }}, null);

    X509HostnameVerifier verifier = new X509HostnameVerifier() {
        @Override
        public void verify(String string, SSLSocket ssls) throws IOException {
        }

        @Override
        public void verify(String string, X509Certificate xc) throws SSLException {
        }

        @Override
        public void verify(String string, String[] strings, String[] strings1) throws SSLException {
        }

        @Override
        public boolean verify(String string, SSLSession ssls) {
            return true;
        }
    };
    final SSLSocketFactory socketFactory = new SSLv3SocketFactory(sslCtx, verifier);
    final SchemeRegistry registry = new SchemeRegistry();
    registry.register(new Scheme("https", 443, socketFactory));

    final PoolingClientConnectionManager cm = new PoolingClientConnectionManager(registry);
    cm.setMaxTotal(100);
    cm.setDefaultMaxPerRoute(50);
    final HttpParams httpParams = new BasicHttpParams();
    HttpConnectionParams.setSoTimeout(httpParams, timeout);

    httpClient = new DefaultHttpClient(cm, httpParams);

    ((DefaultHttpClient) httpClient).setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
        @Override
        public long getKeepAliveDuration(HttpResponse hr, HttpContext hc) {
            return 0;
        }
    });
    httpClient.getParams().setParameter("http.socket.timeout", 900000);

UPD2: 修正SSLSocketFactory(“Received fatal alert: bad_record_mac”)问题

  public class SSLv3SocketFactory extends SSLSocketFactory {

    private final javax.net.ssl.SSLSocketFactory socketfactory;

    public SSLv3SocketFactory(SSLContext sslContext, X509HostnameVerifier hostnameVerifier) {
        super(sslContext, hostnameVerifier);
        this.socketfactory = sslContext.getSocketFactory();
    }

    @Override
    public Socket createLayeredSocket(
            final Socket socket,
            final String host,
            final int port,
            final boolean autoClose) throws IOException, UnknownHostException {
        SSLSocket sslSocket = (SSLSocket) this.socketfactory.createSocket(
                socket,
                host,
                port,
                autoClose);
        sslSocket.setEnabledProtocols(new String[]{"SSLv3"});


        return sslSocket;
    }

    @Override
    public Socket connectSocket(
            final Socket socket,
            final InetSocketAddress remoteAddress,
            final InetSocketAddress localAddress,
            final HttpParams params) throws IOException, UnknownHostException, ConnectTimeoutException {

        if (socket instanceof SSLSocket) {
            ((SSLSocket) socket).setEnabledProtocols(new String[]{"SSLv3"});;
        }
        return super.connectSocket(socket, remoteAddress, localAddress, params);
    }
}

UPD3:问题仅存在于SSLv3中,TLSv1正常运行。

请发布如何在HTTP客户端中执行请求的代码。 - drvdijk
我已添加了HttpClient初始化代码,然后我只需调用httpClient.execute(),URL始终相同。 - John
2个回答

5

HttpClient只有在确保SSL连接属于同一用户/安全上下文时,才会重用带有客户端身份验证的持久SSL连接(出于显而易见的原因)。

确保您对所有逻辑相关的请求使用相同的HttpContext。这将确保安全主体(客户端证书的DN)在单个HTTP请求之间传播。

后续跟进

结果表明,服务器根本不想重用连接。每个响应都包含'Connection: close'指令,提示客户端在接收响应后关闭连接。但是,服务器可能根据请求消息组成对不同的客户端进行不同的处理。尝试通过使用不同的User-Agent标头值来伪装HttpClient,并查看是否有任何差异。


我尝试创建静态HttpContext,并在每次调用httpClient.execute()时指定它。顺便说一下,这个问题只出现在一个服务器上。 - John
如果HttpClient或HttpContext与此有任何关系,我会感到惊讶。 SSL会话支持发生在SSLSocket层,而不是HTTP层。 - user207421
@EJP:恰巧我对HttpClient的内部工作了解相当深入。我说的不是SSL会话或上下文,而是HTTP上下文(其中包括用户安全主体等许多内容),直接影响连接的重用。请参见https://hc.apache.org/httpcomponents-client-ga/httpclient/xref/org/apache/http/impl/client/DefaultUserTokenHandler.html#76 - ok2c
1
@John:请查看后续。 - ok2c
1
@oleg 不是的。这个问题是关于“每个请求都进行SSL握手”的。SSL会话可以跨越TCP连接。它们不受HTTP连接持久性或缺乏持久性的影响。加入现有SSL会话的新TCP连接执行非常简略的SSL握手。 - user207421
显示剩余7条评论

0

正如您在评论中所述,问题仅出现在一个服务器上,显然问题出在该服务器上。他们设置了非常短的SSL会话超时时间,或者以某种方式禁用了会话恢复。

从您的端口无法解决此问题。


C#的HttpSimpleClientProtocol在这个服务器上运行良好,因此我猜想“修复”Java HttpClient是可能的。 - John

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接