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

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

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

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:握手期间远程主机关闭连接
   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)
  在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)
引起:java.io.EOFException:SSL对等关闭不正确
   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个

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”来触发这个。

尝试使用Apache Commons HttpClient库,而不是尝试自己编写 : http : //hc.apache.org/httpclient-3.x/index.html

从他们的示例代码:

  HttpClient httpclient = new HttpClient(); httpclient.getHostConfiguration().setProxy("myproxyhost", 8080); /* Optional if authentication is required. httpclient.getState().setProxyCredentials("my-proxy-realm", " myproxyhost", new UsernamePasswordCredentials("my-proxy-username", "my-proxy-password")); */ PostMethod post = new PostMethod("https://someurl"); NameValuePair[] data = { new NameValuePair("user", "joe"), new NameValuePair("password", "bloggs") }; post.setRequestBody(data); // execute method and handle any error responses. // ... InputStream in = post.getResponseBodyAsStream(); // handle response. /* Example for a GET reqeust GetMethod httpget = new GetMethod("https://someurl"); try { httpclient.executeMethod(httpget); System.out.println(httpget.getStatusLine()); } finally { httpget.releaseConnection(); } */