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

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

问题描述

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

我这样设置我的代理设置:

属性 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 设置如下:

SSLContext sslContext = SSLContext.getInstance(SSL");//建立一个信任一切的 TrustManagersslContext.init( null, new TrustManager[]{新 X509TrustManager(){公共 X509Certificate[] getAcceptedIssuers(){返回空;}public void checkClientTrusted( X509Certificate[] certs, String authType ){//一切都是可信的}public void checkServerTrusted( X509Certificate[] certs, String authType ){//一切都是可信的}}}, new SecureRandom() );//这似乎不适用于通过代理的连接HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());//设置一个主机名验证器来验证一切HttpsURLConnection.setDefaultHostnameVerifier( new HostnameVerifier(){公共布尔验证(字符串 arg0,SSLSession arg1){返回真;}});

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

URL url = new URL( "https://someurl");HttpsURLConnection 连接 = (HttpsURLConnection)url.openConnection();connection.setDoOutput(true);connection.setRequestMethod(POST");connection.setRequestProperty( Content-Type", application/x-www-form-urlencoded");connection.setRequestProperty(内容长度",0");连接.connect();

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

我通常不与 Java 打交道,而且我对 HTTP/Web 内容也没有太多经验.我相信我已经提供了所有必要的细节来理解我想要做什么.如果不是这种情况,请告诉我.

更新:

阅读ZZ Coder推荐的文章后,对连接代码做了如下修改:

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(内容长度",0");连接.connect();

结果(SSLHandshakeException)是一样的.当我将此处的 SLLSocketFactory 设置为 SSLTunnelSocketFactory(文章中解释的类)时,我对 TrustManager 和 SSLContext 所做的事情将被覆盖.我还不需要那个吗?

另一个更新:

我修改了 SSLTunnelSocketFactory 类以使用 SSLSocketFactory,它使用我信任一切的 TrustManager.这似乎没有任何区别.这是 SSLTunnelSocketFactory 的 createSocket 方法:

public Socket createSocket( Socket s, String host, int port, boolean autoClose )抛出 IOException、UnknownHostException{套接字隧道 = 新套接字(隧道主机,隧道端口);doTunnelHandshake(隧道,主机,端口);SSLSocket 结果 = (SSLSocket)dfactory.createSocket(隧道、主机、端口、自动关闭);result.addHandshakeCompletedListener(new HandshakeCompletedListener(){public void handshakeCompleted( HandshakeCompletedEvent 事件){System.out.println( "握手完成!");System.out.println(	 CipherSuite:"+ event.getCipherSuite());System.out.println(	 SessionId"+ event.getSession() );System.out.println(	 PeerHost"+ event.getSession().getPeerHost());}});结果.startHandshake();返回结果;}

我的代码调用connection.connect时,调用了这个方法,调用doTunnelHandshake就成功了.下一行代码使用我的 SSLSocketFactory 创建一个 SSLSocket;此调用后结果的 toString 值为:

1d49247[SSL_NULL_WITH_NULL_NULL: Socket[addr=/proxyHost,port=proxyPort,localport=24372]]".

这对我来说毫无意义,但这可能是事情在此之后崩溃的原因.

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

对于这个日益复杂的谜题,我是否仍然缺少一个重要的部分?

这是堆栈跟踪:

<前>javax.net.ssl.SSLHandshakeException:握手期间远程主机关闭连接在 com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:808)在 com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1112)在 com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1139)在 com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1123)在 gsauthentication.SSLTunnelSocketFactory.createSocket(SSLTunnelSocketFactory.java:106)在 sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:391)在 sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)在 sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:133)在 gsauthentication.GSAuthentication.main(GSAuthentication.java:52)引起:java.io.EOFException:SSL peer关闭不正确在 com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:333)在 com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:789)... 8个

解决方案

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

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

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

如果您想击败 JSSE 中的所有安全措施,您仍然需要自己的 TrustManager.像这样,

 public SSLTunnelSocketFactory(String proxyhost, String proxyport){隧道主机 = 代理主机;隧道端口 = Integer.parseInt(proxyport);dfactory = (SSLSocketFactory)sslContext.getSocketFactory();}...connection.setSSLSocketFactory(new SSLTunnelSocketFactory(proxyHost, proxyPort));connection.setDefaultHostnameVerifier( new HostnameVerifier(){公共布尔验证(字符串 arg0,SSLSession arg1){返回真;}});

编辑 2:我刚刚尝试了我几年前使用 SSLTunnelSocketFactory 编写的程序,但它也不起作用.显然,Sun 在 Java 5 中的某个时候引入了一个新错误.请参阅此错误报告,

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

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

公共类 SSLContextTest {公共静态无效主(字符串 [] args){System.setProperty("https.proxyHost", "proxy.xxx.com");System.setProperty("https.proxyPort", "8888");尝试 {SSLContext sslContext = SSLContext.getInstance("SSL");//建立一个信任一切的 TrustManagersslContext.init(null, new TrustManager[] { new X509TrustManager() {公共 X509Certificate[] getAcceptedIssuers() {System.out.println("getAcceptedIssuers ==============);返回空;}公共无效 checkClientTrusted(X509Certificate[] 证书,字符串身份验证类型) {System.out.println("checkClientTrusted =============");}public void checkServerTrusted(X509Certificate[] 证书,字符串身份验证类型) {System.out.println("checkServerTrusted =============");}} }, new SecureRandom());HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());HttpsURL连接.setDefaultHostnameVerifier(new HostnameVerifier() {公共布尔验证(字符串 arg0,SSLSession arg1){System.out.println("hostnameVerifier =============");返回真;}});URL url = new URL("https://www.verisign.net");URLConnection conn = url.openConnection();BufferedReader 阅读器 =new BufferedReader(new InputStreamReader(conn.getInputStream()));字符串线;while ((line = reader.readLine()) != null) {System.out.println(line);}} 捕获(异常 e){e.printStackTrace();}}}

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

checkServerTrusted ==============主机名验证器 ==============<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">......

如您所见,SSLContext 和 hostnameVerifier 都被调用.HostnameVerifier 仅在主机名与证书不匹配时才涉及.我使用www.verisign.net"来触发这个.

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" );

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;
    }
} );

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();

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.

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.

Update:

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();

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?

Another Update:

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(
                    "	 CipherSuite:" + event.getCipherSuite() );
                System.out.println(
                    "	 SessionId " + event.getSession() );
                System.out.println(
                    "	 PeerHost " + event.getSession().getPeerHost() );
            }
        } );

    result.startHandshake();

    return result;
}

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.

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?

This is the stack trace:

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 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.

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

EDIT: 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;
    }
 }  );

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

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">
......

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天全站免登陆