当前位置:  开发笔记 > 编程语言 > 正文

如何通过Java中的代理发送HTTPS请求?

如何解决《如何通过Java中的代理发送HTTPS请求?》经验,为你挑选了1个好方法。

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

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

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

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("握手期间远程主机关闭连接"):

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方法会被调用; 这也是我在通过代理时需要发生的事情.

我通常不会处理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( "Content-Length", "0" );

connection.connect();

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

另一个更新:

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

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时,将调用此方法,并且对doTunnelHandshake的调用成功.下一行代码使用我的SSLSocketFactory创建一个SSLSocket; 此调用后结果的toString值为:

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

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

当调用result.startHandshake()时,根据调用堆栈HttpsClient.afterConnect,使用相同的参数再次调用相同的createSocket方法,除了Socket s为null,并且当它到达result.startHandshake()时再次,结果是相同的SSLHandshakeException.

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

这是堆栈跟踪:

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

ZZ Coder.. 31

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){
      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.请参阅此bug报告,

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

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

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

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

checkServerTrusted =============
hostnameVerifier =============

......

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



1> ZZ Coder..:

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){
      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.请参阅此bug报告,

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

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

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

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

checkServerTrusted =============
hostnameVerifier =============

......

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

推荐阅读
和谐啄木鸟
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有