2009-10-02 23 views
26

Sto tentando di inviare una richiesta a un server utilizzando la classe HttpsUrlConnection. Il server ha problemi con i certificati, quindi ho configurato un TrustManager che si fida di tutto, così come un verificatore del nome host altrettanto indulgente. Questo gestore funziona perfettamente quando faccio la mia richiesta direttamente, ma non sembra essere usato affatto quando invio la richiesta tramite un proxy.Come inviare una richiesta HTTPS attraverso un proxy in Java?

ho impostato la mia impostazioni proxy in questo modo:

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

Il TrustManager per la SSLSocketFactory default è impostato in questo modo:

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

Se corro il seguente codice, io alla fine con un SSLHandshakException ("Connessione remota dell'host chiuso durante l'handshake"):

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

Presumo che mi manca un qualche tipo di impostazione che ha a che fare con l'utilizzo di un proxy quando si tratta di SSL. Se non utilizzo un proxy, viene chiamato il mio metodo checkServerTrusted; questo è quello che devo succedere quando sto passando attraverso il proxy.

Di solito non mi occupo di Java e non ho molta esperienza con materiale HTTP/web. Credo di aver fornito tutti i dettagli necessari per capire cosa sto cercando di fare. Se questo non è il caso, fammi sapere.

Aggiornamento:

Dopo aver letto l'articolo suggerito da ZZ Coder, ho fatto le seguenti modifiche al codice di connessione:

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

Il risultato (SSLHandshakeException) è lo stesso. Quando imposto qui SLLSocketFactory su SSLTunnelSocketFactory (la classe spiegata nell'articolo), le cose che ho fatto con TrustManager e SSLContext sono sovrascritte. Non ne ho ancora bisogno?

Un altro aggiornamento:

ho modificato la classe SSLTunnelSocketFactory di utilizzare lo SSLSocketFactory che usa il mio TrustManager che si fida di tutto. Non sembra che questo abbia fatto alcuna differenza. Questo è il metodo di createSocket 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; 
} 

Quando il mio codice chiama connection.connect, questo metodo viene chiamato, e la chiamata a doTunnelHandshake è successo. La prossima riga di codice usa il mio SSLSocketFactory per creare un SSLSocket; il valore di toString risultato dopo questa chiamata è:

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

Questo non ha senso per me, ma potrebbe essere la ragione per cui le cose si rompono dopo questo.

Quando viene chiamato result.startHandshake(), viene richiamato lo stesso metodo createSocket, in base allo stack di chiamate, HttpsClient.afterConnect, con gli stessi argomenti, tranne Socket s è null e quando viene visualizzato il risultato .startHandshake() di nuovo, il risultato è la stessa SSLHandshakeException.

Mi manca ancora un pezzo importante per questo puzzle sempre più complicato?

Questa è l'analisi dello stack:

 
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

Ti è mai verificare se il proxy ha fatto chiudere la connessione, vale a dire il proxy ha una convalida del certificato integrata? – sfussenegger

+0

Il messaggio di errore dice "Connessione remota host chiuso durante l'handshake". È il proxy o il server a cui sto tentando di inviare la richiesta? Non ho idea della convalida del certificato. –

+0

Dal punto di vista dei clienti, considererei il proxy remoto. Pertanto, mi assicurerei di (almeno) escludere il proxy come punto di errore. Ho una certa esperienza con i negozi di fiducia e su quanto possa essere talvolta doloroso SSL. Ma non ho mai visto un'eccezione del genere. Non ho mai usato un proxy con SSL, ma all'inizio dovrei assicurarmi che il proxy non stia facendo alcun danno alla tua connessione. – sfussenegger

risposta

28

proxy HTTPS non ha senso perché non si può sospendere l'connessione HTTP al proxy per motivi di sicurezza. Con la tua politica di fiducia, potrebbe funzionare se il server proxy ha una porta HTTPS. Il tuo errore è causato dalla connessione alla porta proxy HTTP con HTTPS.

È possibile connettersi tramite un proxy utilizzando il tunneling SSL (molti chiamano quel proxy) utilizzando il comando CONNECT proxy. Tuttavia, Java non supporta la versione più recente del tunneling del proxy. In tal caso, devi gestire tu stesso il tunneling. È possibile trovare il codice di esempio qui,

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

EDIT: Se vuoi sconfiggere tutte le misure di sicurezza in JSSE, è ancora necessario il proprio TrustManager. Qualcosa di simile,

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: Ho appena provato il mio programma che ho scritto qualche anno fa utilizzando SSLTunnelSocketFactory e non funziona neanche. A quanto pare, Sun ha introdotto un nuovo bug a volte nel Java 5. Vai a questa segnalazione di bug,

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

La buona notizia è che il bug SSL tunneling è fissato in modo che si può semplicemente utilizzare la fabbrica di default. Ho appena provato con un proxy e tutto funziona come previsto. Vedere il mio codice,

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

Questo è ciò che ottengo quando faccio funzionare il programma,

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

Come si può vedere, sia SSLContext e hostnameVerifier sono sempre chiamati. HostnameVerifier è coinvolto solo quando il nome host non corrisponde al certificato. Ho usato "www.verisign.net" per attivare questo.

+0

Se uso la classe SSLTunnelSocketFactory discussa in questo articolo, sovrascrive il materiale predefinito di TrustManager che ho inserito prima. Ne ho ancora bisogno? –

+0

Il trust è tra il browser e il server finale. Non ha niente a che fare con il tunnel. Ne hai ancora bisogno. –

+0

Non vedo come posso ancora usarlo con la classe SSLTunnelSocketFactory. Sembra che io possa usare solo l'uno o l'altro. –

0

Provate i Comuni Apache HttpClient biblioteca invece di cercare di rotolare il proprio: http://hc.apache.org/httpclient-3.x/index.html

Dal loro codice di esempio:

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

Un mio amico mi ha consigliato questa settimana scorsa. L'ho scaricato, ma non ho ancora avuto la possibilità di provarlo. Ci proverò per prima cosa domani mattina. –

Problemi correlati