2009-10-02 16 views
26

Próbuję wysłać żądanie do serwera przy użyciu klasy HttpsUrlConnection. Serwer ma problemy z certyfikatami, więc ustawiam TrustManager, który ufa wszystkim, a także weryfikator nazwy hosta, który jest równie łagodny. Ten menedżer działa dobrze, gdy bezpośrednio wysyłam żądanie, ale wydaje się, że nie jest on w ogóle używany, gdy wysyłam żądanie za pośrednictwem serwera proxy.Jak wysłać żądanie HTTPS przez serwer proxy w Javie?

ustawić moje ustawienia serwera proxy tak:

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

TrustManager dla domyślnego SSLSocketFactory jest skonfigurowany tak:

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

Jeśli uruchomić poniższy kod, ja skończyć z SSLHandshakException ("Zdalne host zamknięte połączenie podczas uzgadniania"):

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

Zakładam, że brakuje mi jakiegoś ustawienia związanego z używaniem proxy podczas pracy z SSL. Jeśli nie używam proxy, moja metoda checkServerTrusted zostanie wywołana; to jest to, co muszę zrobić, kiedy również przechodzę przez proxy.

Zazwyczaj nie mam do czynienia z Javą i nie mam dużego doświadczenia z materiałami HTTP/sieciowymi. Uważam, że dostarczyłem wszystkich szczegółów niezbędnych do zrozumienia tego, co próbuję zrobić. Jeśli tak nie jest, daj mi znać.

Aktualizacja:

Po przeczytaniu artykułu zaproponowaną przez ZZ Coder, zrobiłem następujące zmiany w kodzie gra:

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

Wynik (SSLHandshakeException) jest taka sama. Kiedy ustawię SLLSocketFactory tutaj na SSLTunnelSocketFactory (klasa wyjaśniona w artykule), rzeczy zrobione z TrustManager i SSLContext zostaną nadpisane. Czy nadal tego nie potrzebuję?

Kolejna aktualizacja:

zmodyfikowałem SSLTunnelSocketFactory klasę używać SSLSocketFactory który używa mojego TrustManager że ufa wszystko. Nie wydaje się, żeby to miało jakikolwiek wpływ. Jest to metoda createSocket z 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; 
} 

Kiedy mój kod wywołuje connection.connect, metoda ta jest wywoływana, a wezwanie do doTunnelHandshake jest udany. Następna linia kodu używa mojego SSLSocketFactory do utworzenia SSLSocket; wartość toString rezultacie po tej rozmowy jest:

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

Jest to dla mnie bez znaczenia, ale może to być przyczyną, dla której po tym nastąpi załamanie.

Kiedy wywoływana jest metoda result.startHandshake(), ta sama metoda createSocket jest wywoływana ponownie, zgodnie ze stosem wywołań, HttpsClient.afterConnect, z tymi samymi argumentami, z wyjątkiem Socket s ma wartość null, a gdy przychodzi do wyniku .startHandshake() ponownie, wynik jest taki sam SSLHandshakeException.

Czy nadal brakuje mi ważnej części tej coraz bardziej skomplikowanej układanki?

To jest ślad stosu:

 
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

Czy kiedykolwiek sprawdzić, czy serwer proxy tak ścisły związek, czyli pełnomocnika ma wbudowaną walidację certyfikatu? – sfussenegger

+0

Komunikat o błędzie "Zamknięte połączenie zdalnego hosta podczas uzgadniania". Czy to proxy, czy serwer, na który próbuję wysłać żądanie? Nie mam pojęcia o walidacji certyfikatu. –

+0

Z punktu widzenia klienta uważam, że proxy też jest odległe. Dlatego powinienem (przynajmniej) wykluczyć proxy jako punkt awarii. Mam pewne doświadczenie z magazynami zaufania i jak bolesne może być czasami SSL. Ale nigdy nie widziałem takiego wyjątku. Nigdy nie korzystałem z serwera proxy z protokołem SSL, ale najpierw upewniłem się, że serwer proxy nie wyrządza żadnej szkody w twoim połączeniu. – sfussenegger

Odpowiedz

28

HTTPS proxy nie ma sensu, ponieważ nie można zakończyć połączenia HTTP na serwerze proxy ze względów bezpieczeństwa. Dzięki zasadom zaufania może działać, jeśli serwer proxy ma port HTTPS. Twój błąd jest spowodowany połączeniem z portem proxy HTTP za pomocą protokołu HTTPS.

Możesz połączyć się przez serwer proxy za pomocą tunelowania SSL (wiele osób nazywa to proxy) za pomocą polecenia POPRZEZ połączenie. Jednak Java nie obsługuje nowszej wersji tunelowania proxy. W takim przypadku musisz sam sobie poradzić z tunelowaniem. Można znaleźć przykładowy kod tutaj,

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

EDIT: Jeśli chcesz pokonać wszelkie środki bezpieczeństwa w JSSE, trzeba jeszcze własnego TrustManager. Coś takiego,

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: Właśnie próbowałem mój program napisałem kilka lat temu przy użyciu SSLTunnelSocketFactory i to nie działa. Podobno Sun wprowadził nowy błąd kiedyś w Javie 5. Zobacz ten raport o błędach,

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

Dobrą wiadomością jest to, że błąd tunelowanie SSL jest stała, więc można po prostu użyć fabrycznych. Po prostu próbowałem z proxy i wszystko działało zgodnie z oczekiwaniami. Zobacz mój kod,

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

To co mam, kiedy uruchomić program,

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

Jak widać, zarówno SSLContext i hostnameVerifier nazywane są coraz. HostnameVerifier występuje tylko wtedy, gdy nazwa hosta nie jest zgodna z certyfikatem. Użyłem "www.verisign.net", aby to uruchomić.

+0

Jeśli używam klasy SSLTunnelSocketFactory omawianej w tym artykule, zastępuje ona domyślne rzeczy, które umieszczam wcześniej w aplikacji TrustManager. Czy nadal tego potrzebuję? –

+0

Zaufanie jest między przeglądarką a serwerem końcowym. Nie ma nic wspólnego z tunelem. Nadal go potrzebujesz. –

+0

Nie widzę sposobu, w jaki można nadal używać go z klasą SSLTunnelSocketFactory. Wygląda na to, że mogę używać tylko jednego lub drugiego. –

0

Spróbuj Commons Apache httpclient bibliotekę zamiast próbować toczyć własne: http://hc.apache.org/httpclient-3.x/index.html

Z ich przykładowy kod:

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

Mój przyjaciel polecił mi to w zeszłym tygodniu. Pobrałem go, ale jeszcze nie miałem okazji go wypróbować. Spróbuję tego jutro rano. –