如何通过Java中的代理发送HTTPS请求? [英] How do a send an HTTPS request through a proxy in Java?

查看:2145
本文介绍了如何通过Java中的代理发送HTTPS请求?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用HttpsUrlConnection类向服务器发送请求。服务器有证书问题,所以我设置了一个信任所有东西的TrustManager,以及一个同样宽松的主机名验证器。当我直接提出请求时,这位经理工作得很好,但是当我通过代理发送请求时,它似乎根本就没用过。

I am trying to send a request to a server using the HttpsUrlConnection class. The server has certificate issues, so I set up a TrustManager that trusts everything, as well as a hostname verifier that is equally lenient. This manager works just fine when I make my request directly, but it doesn't seem to be used at all when I send the request through a proxy.

我设置了我的代理设置如下:

I set my proxy settings like this:

Properties systemProperties = System.getProperties();
systemProperties.setProperty( "http.proxyHost", "proxyserver" );
systemProperties.setProperty( "http.proxyPort", "8080" );
systemProperties.setProperty( "https.proxyHost", "proxyserver" );
systemProperties.setProperty( "https.proxyPort", "8080" );

默认SSLSocketFactory的TrustManager设置如下:

The TrustManager for the default SSLSocketFactory is set up like this:

SSLContext sslContext = SSLContext.getInstance( "SSL" );

// set up a TrustManager that trusts everything
sslContext.init( null, new TrustManager[]
    {
        new X509TrustManager()
        {
            public X509Certificate[] getAcceptedIssuers()
            {
                return null;
            }

            public void checkClientTrusted( X509Certificate[] certs, String authType )
            {
                // everything is trusted
            }

            public void checkServerTrusted( X509Certificate[] certs, String authType )
            {
                // everything is trusted
            }
        }
    }, new SecureRandom() );

// this doesn't seem to apply to connections through a proxy
HttpsURLConnection.setDefaultSSLSocketFactory( sslContext.getSocketFactory() );

// setup a hostname verifier that verifies everything
HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier()
{
    public boolean verify( String arg0, SSLSession arg1 )
    {
        return true;
    }
} );

如果我运行以下代码,我最终会遇到SSLHandshakException(握手期间远程主机关闭连接 ):

If I run the following code, I end up with an SSLHandshakException ("Remote host closed connection during handshake"):

URL url = new URL( "https://someurl" );

HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setDoOutput( true );

connection.setRequestMethod( "POST" );
connection.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
connection.setRequestProperty( "Content-Length", "0" );

connection.connect();

我假设我在处理SSL时缺少某种与使用代理有关的设置。如果我不使用代理,我的checkServerTrusted方法会被调用;这也是我在通过代理时需要发生的事情。

I assume I am missing some kind of setting having to do with using a proxy when dealing with SSL. If I don't use a proxy, my checkServerTrusted method gets called; this is what I need to happen when I am going through the proxy as well.

我通常不会处理Java而且我对HTTP没有太多经验/ web的东西。我相信我已经提供了所有必要的细节,以了解我想要做什么。如果情况并非如此,请与我们联系。

I don't usually deal with Java and I don't have much experience with HTTP/web stuff. I believe I have provided all the detail necessary to understand what I am trying to do. If this isn't the case, let me know.

更新:

在阅读ZZ Coder建议的文章后,我对连接代码进行了以下更改:

After reading the article suggested by ZZ Coder, I made the following changes to the connection code:

HttpsURLConnection connection = (HttpsURLConnection)url.openConnection();
connection.setSSLSocketFactory( new SSLTunnelSocketFactory( proxyHost, proxyPort ) );

connection.setDoOutput( true );
connection.setRequestMethod( "POST" );
connection.setRequestProperty( "Content-Type", "application/x-www-form-urlencoded" );
connection.setRequestProperty( "Content-Length", "0" );

connection.connect();

结果(SSLHandshakeException)是相同的。当我在这里将SLLSocketFactory设置为SSLTunnelSocketFactory(文章中解释的类)时,我对TrustManager和SSLContext所做的事情被覆盖了。我还不需要吗?

The result (SSLHandshakeException) is the same. When I set the SLLSocketFactory here to the SSLTunnelSocketFactory (the class explained in the article), the stuff I did with the TrustManager and the SSLContext is overridden. Don't I still need that?

另一次更新:

我修改了SSLTunnelSocketFactory类使用SSLSocketFactory,它使用我信任所有东西的TrustManager。这似乎没有任何区别。这是SSLTunnelSocketFactory的createSocket方法:

I modified the SSLTunnelSocketFactory class to use the SSLSocketFactory that uses my TrustManager that trusts everything. It doesn't appear that this has made any difference. This is the createSocket method of SSLTunnelSocketFactory:

public Socket createSocket( Socket s, String host, int port, boolean autoClose )
    throws IOException, UnknownHostException
{
    Socket tunnel = new Socket( tunnelHost, tunnelPort );

    doTunnelHandshake( tunnel, host, port );

    SSLSocket result = (SSLSocket)dfactory.createSocket(
        tunnel, host, port, autoClose );

    result.addHandshakeCompletedListener(
        new HandshakeCompletedListener()
        {
            public void handshakeCompleted( HandshakeCompletedEvent event )
            {
                System.out.println( "Handshake finished!" );
                System.out.println(
                    "\t CipherSuite:" + event.getCipherSuite() );
                System.out.println(
                    "\t SessionId " + event.getSession() );
                System.out.println(
                    "\t PeerHost " + event.getSession().getPeerHost() );
            }
        } );

    result.startHandshake();

    return result;
}

当我的代码调用connection.connect时,调用此方法,并调用toTunnelHandshake是成功的。下一行代码使用我的SSLSocketFactory创建一个SSLSocket;此调用后结果的toString值为:

When my code calls connection.connect, this method is called, and the call to doTunnelHandshake is successful. The next line of code uses my SSLSocketFactory to create an SSLSocket; the toString value of result after this call is:

1d49247 [SSL_NULL_WITH_NULL_NULL:Socket [addr = / proxyHost,port = proxyPort,localport = 24372]] 即可。

这对我来说毫无意义,但这可能是事情发生后失败的原因。

This is meaningless to me, but it might be the reason things break down after this.

结果.startHandshake()被调用,同样的createSocket方法再次被调用,根据调用堆栈,HttpsClient.afterConnect,具有相同的参数,除了Socket s为null,并且当它再次出现result.startHandshake()时,结果是相同的SSLHandshakeException。

When result.startHandshake() is called, the same createSocket method is called again from, according to the call stack, HttpsClient.afterConnect, with the same arguments, except Socket s is null, and when it comes around to result.startHandshake() again, the result is the same SSLHandshakeException.

我是否仍然错过了这个日益复杂的难题的重要部分?

Am I still missing an important piece to this increasingly complicated puzzle?

这是堆栈跟踪:


javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:808)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1112)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1139)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1123)
  at gsauthentication.SSLTunnelSocketFactory.createSocket(SSLTunnelSocketFactory.java:106)
  at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:391)
  at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)
  at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:133)
  at gsauthentication.GSAuthentication.main(GSAuthentication.java:52)
Caused by: java.io.EOFException: SSL peer shut down incorrectly
  at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:333)
  at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:789)
  ... 8 more


推荐答案

HTTPS代理没有意义,因为你无法终止你的HTTP连接代理出于安全原因。使用您的信任策略,如果代理服务器具有HTTPS端口,它可能会起作用。您的错误是由使用HTTPS连接到HTTP代理端口引起的。

HTTPS proxy doesn't make sense because you can't terminate your HTTP connection at the proxy for security reasons. With your trust policy, it might work if the proxy server has a HTTPS port. Your error is caused by connecting to HTTP proxy port with HTTPS.

您可以使用代理CONNECT命令通过代理使用SSL隧道(许多人调用该代理)进行连接。但是,Java不支持较新版本的代理隧道。在这种情况下,您需要自己处理隧道。你可以在这里找到示例代码,

You can connect through a proxy using SSL tunneling (many people call that proxy) using proxy CONNECT command. However, Java doesn't support newer version of proxy tunneling. In that case, you need to handle the tunneling yourself. You can find sample code here,

http://www.javaworld.com/javaworld/javatips/jw-javatip111.html

编辑:如果你想打败所有的安全在JSSE中,您仍然需要自己的TrustManager。像这样,

If you want defeat all the security measures in JSSE, you still need your own TrustManager. Something like this,

 public SSLTunnelSocketFactory(String proxyhost, String proxyport){
      tunnelHost = proxyhost;
      tunnelPort = Integer.parseInt(proxyport);
      dfactory = (SSLSocketFactory)sslContext.getSocketFactory();
 }

 ...

 connection.setSSLSocketFactory( new SSLTunnelSocketFactory( proxyHost, proxyPort ) );
 connection.setDefaultHostnameVerifier( new HostnameVerifier()
 {
    public boolean verify( String arg0, SSLSession arg1 )
    {
        return true;
    }
 }  );

编辑2:我刚刚尝试过我几年前使用SSLTunnelSocketFactory编写的程序而它没有工作要么。显然,Sun在Java 5中引入了一个新的bug。请参阅此错误报告,

EDIT 2: I just tried my program I wrote a few years ago using SSLTunnelSocketFactory and it doesn't work either. Apparently, Sun introduced a new bug sometime in Java 5. See this bug report,

http://bugs.sun.com/view_bug.do?bug_id=6614957

好消息是SSL隧道错误修复,所以你可以使用默认工厂。我只是尝试使用代理,一切都按预期工作。查看我的代码,

The good news is that the SSL tunneling bug is fixed so you can just use the default factory. I just tried with a proxy and everything works as expected. See my code,

public class SSLContextTest {

    public static void main(String[] args) {

        System.setProperty("https.proxyHost", "proxy.xxx.com");
        System.setProperty("https.proxyPort", "8888");

        try {

            SSLContext sslContext = SSLContext.getInstance("SSL");

            // set up a TrustManager that trusts everything
            sslContext.init(null, new TrustManager[] { new X509TrustManager() {
                public X509Certificate[] getAcceptedIssuers() {
                    System.out.println("getAcceptedIssuers =============");
                    return null;
                }

                public void checkClientTrusted(X509Certificate[] certs,
                        String authType) {
                    System.out.println("checkClientTrusted =============");
                }

                public void checkServerTrusted(X509Certificate[] certs,
                        String authType) {
                    System.out.println("checkServerTrusted =============");
                }
            } }, new SecureRandom());

            HttpsURLConnection.setDefaultSSLSocketFactory(
                    sslContext.getSocketFactory());

            HttpsURLConnection
                    .setDefaultHostnameVerifier(new HostnameVerifier() {
                        public boolean verify(String arg0, SSLSession arg1) {
                            System.out.println("hostnameVerifier =============");
                            return true;
                        }
                    });

            URL url = new URL("https://www.verisign.net");
            URLConnection conn = url.openConnection();
            BufferedReader reader = 
                new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }
}

这是我得到的我运行程序,

This is what I get when I run the program,

checkServerTrusted =============
hostnameVerifier =============
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
......

正如你所看到的,SSLContext和hostnameVerifier都得到了调用。仅当主机名与证书不匹配时才涉及HostnameVerifier。我用www.verisign.net来触发这个。

As you can see, both SSLContext and hostnameVerifier are getting called. HostnameVerifier is only involved when the hostname doesn't match the cert. I used "www.verisign.net" to trigger this.

这篇关于如何通过Java中的代理发送HTTPS请求?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆