2009-10-02 8 views
26

Ich versuche mit der HttpsUrlConnection-Klasse eine Anfrage an einen Server zu senden. Der Server hat Probleme mit Zertifikaten, deshalb habe ich einen TrustManager eingerichtet, der alles vertraut, sowie einen Hostnamen-Verifikator, der gleichermaßen nachsichtig ist. Dieser Manager funktioniert einwandfrei, wenn ich meine Anfrage direkt mache, aber es scheint überhaupt nicht verwendet zu werden, wenn ich die Anfrage über einen Proxy sende.Wie kann ich eine HTTPS-Anfrage über einen Proxy in Java senden?

ich meine Proxy-Einstellungen wie folgt aus:

Properties systemProperties = System.getProperties(); 
systemProperties.setProperty("http.proxyHost", "proxyserver"); 
systemProperties.setProperty("http.proxyPort", "8080"); 
systemProperties.setProperty("https.proxyHost", "proxyserver"); 
systemProperties.setProperty("https.proxyPort", "8080"); 

Die Trustmanager für den Standard SSLSocketFactory ist wie folgt aufgebaut:

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

Wenn ich den folgenden Code ausführen, ich mit einem Ende SSLHandshakException ("Remote-Host-Verbindung während Handshake geschlossen"):

Ich gehe davon aus, dass mir eine Einstellung fehlt, bei der es um den Umgang mit SSL geht. Wenn ich keinen Proxy verwende, wird meine checkServerTrusted-Methode aufgerufen. das ist es, was ich tun muss, wenn ich den Proxy durchgehe.

Ich normalerweise nicht mit Java und ich habe nicht viel Erfahrung mit HTTP/Web-Zeug. Ich glaube, ich habe alle notwendigen Details zur Verfügung gestellt, um zu verstehen, was ich versuche zu tun. Wenn das nicht der Fall ist, lass es mich wissen.

Update:

Nach dem Artikel vorgeschlagen von ZZ Coder lesen, habe ich die folgenden Änderungen an den Verbindungscode:

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

Das Ergebnis (SSLHandshakeException) ist das gleiche. Wenn ich die SLLSocketFactory hier auf die SSLTunnelSocketFactory (die im Artikel erläuterte Klasse) setze, werden die Sachen, die ich mit dem TrustManager und dem SSLContext gemacht habe, außer Kraft gesetzt. Brauche ich das noch?

Ein weiteres Update:

modifizierte ich die SSLTunnelSocketFactory Klasse die SSLSocketFactory zu verwenden, die meine Trustmanager verwendet, der alles vertraut. Es scheint nicht, dass dies einen Unterschied gemacht hat. Dies ist die Methode create von 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(
        "\t CipherSuite:" + event.getCipherSuite()); 
       System.out.println(
        "\t SessionId " + event.getSession()); 
       System.out.println(
        "\t PeerHost " + event.getSession().getPeerHost()); 
      } 
     }); 

    result.startHandshake(); 

    return result; 
} 

Wenn mein Code ruft connection.connect, diese Methode aufgerufen wird, und der Aufruf an doTunnelHandshake erfolgreich ist. Die nächste Codezeile verwendet meine SSLSocketFactory, um eine SSLSocket zu erstellen. der toString Wert des Ergebnisses nach diesem Aufruf ist:

"1d49247 [SSL_NULL_WITH_NULL_NULL: Socket [adr =/proxyhost, port = proxy, LokalerAnschluss = 24372]]".

Das ist für mich bedeutungslos, aber es könnte der Grund dafür sein, dass die Dinge danach zusammenbrechen.

Wenn result.startHandshake() aufgerufen wird, wird die gleiche createSocket -Methode erneut aufgerufen, entsprechend dem Aufruf-Stack HttpsClient.afterConnect, mit denselben Argumenten, außer Socket s ist null, und wenn es zum Ergebnis kommt .startHandshake() erneut, das Ergebnis ist dieselbe SSLHandshakeException.

Fehle mir immer noch ein wichtiges Stück dieses immer komplizierteren Puzzles?

Dies ist der 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 
+0

Haben Sie schon einmal überprüfen, ob der Proxy geschlossen wurde die Verbindung, dh der Proxy hat eine integrierte Zertifikatsprüfung? – sfussenegger

+0

Die Fehlermeldung lautet "Remote-Host hat Verbindung während Handshake geschlossen". Ist das der Proxy oder der Server, an den ich die Anfrage senden möchte? Ich habe keine Ahnung von der Zertifikatsvalidierung. –

+0

Aus Sicht des Clients würde ich den Proxy auch als remote betrachten. Daher würde ich sicherstellen, dass der Proxy als Fehlerquelle (zumindest) ausgeschlossen wird. Ich habe einige Erfahrung mit Trust Stores und wie schmerzhaft SSL manchmal sein kann. Aber ich habe noch nie eine solche Ausnahme gesehen. Ich habe nie einen Proxy mit SSL benutzt, aber als Erstes würde ich sicherstellen, dass der Proxy Ihrer Verbindung keinen Schaden zufügt. – sfussenegger

Antwort

28

HTTPS-Proxy macht keinen Sinn, weil Sie nicht Ihre HTTP-Verbindung an den Proxy aus Sicherheitsgründen beenden. Mit Ihrer Vertrauensrichtlinie könnte es funktionieren, wenn der Proxy-Server einen HTTPS-Port hat. Ihr Fehler wird durch die Verbindung zum HTTP-Proxy-Port mit HTTPS verursacht.

Sie können eine Verbindung über einen Proxy mithilfe von SSL-Tunneling herstellen (viele Benutzer nennen diesen Proxy), indem Sie den Proxy-Befehl CONNECT verwenden. Java unterstützt jedoch keine neuere Version von Proxy-Tunneling. In diesem Fall müssen Sie das Tunneln selbst durchführen. Sie können Beispielcode finden Sie hier,

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

EDIT: Wenn Sie alle Sicherheitsmaßnahmen in JSSE besiegen wollen, können Sie immer noch Ihre eigene Trustmanager benötigen. So etwas wie dies,

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: Ich habe mein Programm nur habe ich versucht, ein paar Jahren schrieb vor SSLTunnelSocketFactory verwenden und es funktioniert auch nicht. Offenbar führte Sun einen neuen Fehler irgendwann in Java 5. diesen Fehlerbericht anzeigen,

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

Die gute Nachricht ist, dass das SSL-Tunneling Fehler behoben ist, so dass Sie nur die Standard-Fabrik nutzen können. Ich habe gerade versucht mit einem Proxy und alles funktioniert wie erwartet. Sehen Sie meinen 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(); 
     } 
    } 
} 

Dies ist, was ich bekomme, wenn ich das Programm ausführen,

checkServerTrusted ============= 
hostnameVerifier ============= 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"> 
...... 

Wie Sie sehen können, sowohl SSL-Kontext und hostnameVerifier genannt zu werden. HostnameVerifier ist nur beteiligt, wenn der Hostname nicht mit dem Zertifikat übereinstimmt. Ich habe "www.verisign.net" benutzt, um das auszulösen.

+0

Wenn ich die SSLTunnelSocketFactory-Klasse in diesem Artikel diskutiert, überschreibt es die Standard-TrustManager-Zeug, das ich vor. Brauche ich das noch? –

+0

Die Vertrauensstellung zwischen Browser und dem endgültigen Server. Es hat nichts mit Tunnel zu tun. Du brauchst es immer noch. –

+0

Ich sehe keinen Weg, ich kann es immer noch mit der SSLTunnelSocketFactory-Klasse verwenden. Es scheint, dass ich nur das eine oder das andere benutzen kann. –

0

versuchen, den Apache Commons Httpclient-Bibliothek statt zu versuchen, Ihre eigene Rolle: http://hc.apache.org/httpclient-3.x/index.html

Von ihrem Beispielcode:

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(); 
    } 
    */ 
+0

Ein Freund von mir empfahl mir diese letzte Woche. Ich habe es heruntergeladen, aber ich hatte noch keine Gelegenheit es zu versuchen. Ich werde es morgen früh als erstes versuchen. –