在Java中,用户如何通过代理发送HTTPS请求?
我正在尝试使用HttpsUrlConnection类向服务器发送请求。服务器有证书问题,所以我设置了信任一切的TrustManager,以及同样宽松的主机名验证程序。当我直接提出请求时,这个管理器工作得很好,但当我通过代理发送请求时,它似乎根本没有被使用 我将代理设置如下:在Java中,用户如何通过代理发送HTTPS请求?,java,proxy,sslhandshakeexception,Java,Proxy,Sslhandshakeexception,我正在尝试使用HttpsUrlConnection类向服务器发送请求。服务器有证书问题,所以我设置了信任一切的TrustManager,以及同样宽松的主机名验证程序。当我直接提出请求时,这个管理器工作得很好,但当我通过代理发送请求时,它似乎根本没有被使用 我将代理设置如下: Properties systemProperties = System.getProperties(); systemProperties.setProperty( "http.proxyHost",
Properties systemProperties = System.getProperties();
systemProperties.setProperty( "http.proxyHost", "proxyserver" );
systemProperties.setProperty( "http.proxyPort", "8080" );
systemProperties.setProperty( "https.proxyHost", "proxyserver" );
systemProperties.setProperty( "https.proxyPort", "8080" );
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;
}
} );
默认SSLSocketFactory的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" );
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(“握手期间远程主机关闭连接”):
我假设在处理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类,以使用使用信任一切的TrustManager的SSLSocketFactory。这似乎没有什么不同。这是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\u NULL\u WITH\u NULL\u NULL:Socket[addr=/proxyHost,port=proxyPort,localport=24372]”
这对我来说毫无意义,但这可能是事情在这之后崩溃的原因
调用result.startHandshake()时,根据调用堆栈,使用相同的参数从HttpsClient.afterConnect再次调用相同的createSocket方法,但套接字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
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对等机错误关闭
位于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代理端口引起的 您可以使用proxy connect命令使用SSL隧道(许多人称之为代理)通过代理进行连接。但是,Java不支持更新版本的代理隧道。在这种情况下,您需要自己处理隧道问题。您可以在这里找到示例代码 编辑:如果你想击败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在Java5中引入了一个新的bug。看看这个bug报告
好消息是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 =============
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
......
checkServerTrusted=============
主机名验证程序=============
......
如您所见,SSLContext和hostnameVerifier都被调用。HostnameVerifier仅在主机名与证书不匹配时才参与。我使用“www.verisign.net”触发此操作。尝试使用Apache Commons HttpClient库,而不是尝试使用自己的库: 从他们的示例代码:
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();
}
*/
如果我使用本文中讨论的SSLTunnelSocketFactory类,它将覆盖我之前输入的默认TrustManager内容。我还需要吗?信任是在浏览器和最终服务器之间。这与隧道无关。你仍然需要它。我看不出还有什么方法可以在SSLTunnelSocketFactory类中使用它。看来我只能用其中一个,你两个都可以用。只要按照你的方式创建数据工厂。@ZZ编码员:我相信我已经听从了你的建议,但我还没有看到任何变化。我一定还有别的事要做。如果你还有什么建议,我会很感激的。你有没有检查过代理是否关闭了康涅狄格州