我正在尝试使用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"来触发这个.
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"来触发这个.