web-dev-qa-db-fra.com

Comment envoyer une requête HTTPS via un proxy en Java?

J'essaie d'envoyer une demande à un serveur à l'aide de la classe HttpsUrlConnection. Le serveur a des problèmes de certificat, j'ai donc mis en place un TrustManager qui fait confiance à tout, ainsi qu'un vérificateur de nom d'hôte qui est tout aussi indulgent. Ce gestionnaire fonctionne très bien lorsque je fais ma demande directement, mais il ne semble pas du tout être utilisé lorsque j'envoie la demande via un proxy.

J'ai défini mes paramètres de proxy comme ceci:

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

Le TrustManager pour le SSLSocketFactory par défaut est configuré comme suit:

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

Si j'exécute le code suivant, je me retrouve avec une SSLHandshakException ("Connexion fermée de l'hôte distant pendant la prise de contact"):

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

Je suppose qu'il me manque une sorte de paramètre lié à l'utilisation d'un proxy lors de l'utilisation de SSL. Si je n'utilise pas de proxy, ma méthode checkServerTrusted est appelée; c'est ce que je dois faire lorsque je passe par le proxy également.

Je ne m'occupe généralement pas de Java et je n'ai pas beaucoup d'expérience avec les trucs HTTP/Web. Je pense avoir fourni tous les détails nécessaires pour comprendre ce que j'essaie de faire. Si ce n'est pas le cas, faites le moi savoir.

Mise à jour:

Après avoir lu l'article suggéré par ZZ Coder, j'ai apporté les modifications suivantes au code de connexion:

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

Le résultat (SSLHandshakeException) est le même. Lorsque j'ai défini SLLSocketFactory ici sur SSLTunnelSocketFactory (la classe expliquée dans l'article), les choses que j'ai faites avec TrustManager et SSLContext sont remplacées. N'ai-je pas encore besoin de ça?

ne autre mise à jour:

J'ai modifié la classe SSLTunnelSocketFactory pour utiliser SSLSocketFactory qui utilise mon TrustManager qui fait confiance à tout. Il ne semble pas que cela ait fait de différence. Il s'agit de la méthode createSocket de 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;
}

Lorsque mon code appelle connection.connect, cette méthode est appelée et l'appel à doTunnelHandshake réussit. La ligne de code suivante utilise mon SSLSocketFactory pour créer un SSLSocket; la valeur toString du résultat après cet appel est:

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

Cela n'a aucun sens pour moi, mais c'est peut-être la raison pour laquelle les choses s'effondrent après cela.

Lorsque result.startHandshake () est appelé, la même méthode createSocket est appelée à nouveau depuis, selon la pile d'appels, HttpsClient.afterConnect, avec les mêmes arguments, sauf que Socket s est nul, et lorsqu'il revient à result.startHandshake () encore une fois, le résultat est la même exception SSLHandshakeException.

Me manque-t-il encore une pièce importante dans ce puzzle de plus en plus compliqué?

Voici la trace de la pile:

 javax.net.ssl.SSLHandshakeException: connexion fermée de l'hôte distant pendant la prise de contact 
 sur com.Sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord (SSLSocketImpl.Java:808) 
 sur com.Sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake (SSLSocketImpl.Java:1112) 
 sur com.Sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake (SSLSocketImpl.Java : 1139) 
 Sur com.Sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake (SSLSocketImpl.Java:1123) 
 Sur gsauthentication.SSLTunnelSocketFactory.createSocket (SSLTunnelSocketFactory.Java:106) 
 sur Sun.net.www.protocol.https.HttpsClient.afterConnect (HttpsClient.Java:391) 
 sur Sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect (AbstractDelegateHttpsURLConnection.Java:166 ) 
 sur Sun.net.www.protocol.https.HttpsURLConnectionImpl.connect (HttpsURLConnectionImpl.Java:133) 
 sur gsauthentication.GSAuthentication.main (GSAuthentication.Java:52) 
 Causé par: Java.io.EOFExce ption: homologue SSL arrêté de façon incorrecte 
 sur com.Sun.net.ssl.internal.ssl.InputRecord.read (InputRecord.Java:333) 
 sur com.Sun.net.ssl.internal .ssl.SSLSocketImpl.readRecord (SSLSocketImpl.Java:789) 
 ... 8 de plus 
27
Jeff Hillman

Le proxy HTTPS n'a pas de sens car vous ne pouvez pas mettre fin à votre connexion HTTP sur le proxy pour des raisons de sécurité. Avec votre stratégie d'approbation, cela peut fonctionner si le serveur proxy possède un port HTTPS. Votre erreur est due à la connexion au port proxy HTTP avec HTTPS.

Vous pouvez vous connecter via un proxy en utilisant le tunneling SSL (beaucoup de gens appellent ce proxy) en utilisant la commande proxy CONNECT. Cependant, Java ne prend pas en charge la nouvelle version du tunneling proxy. Dans ce cas, vous devez gérer le tunneling vous-même. Vous pouvez trouver un exemple de code ici,

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

EDIT: Si vous souhaitez contourner toutes les mesures de sécurité dans JSSE, vous avez toujours besoin de votre propre TrustManager. Quelque chose comme ça,

 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: Je viens d'essayer mon programme que j'ai écrit il y a quelques années en utilisant SSLTunnelSocketFactory et cela ne fonctionne pas non plus. Apparemment, Sun a introduit un nouveau bogue dans Java 5. Voir ce rapport de bogue,

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

La bonne nouvelle est que le bogue de tunneling SSL est corrigé afin que vous puissiez simplement utiliser la fabrique par défaut. Je viens d'essayer avec un proxy et tout fonctionne comme prévu. Voir mon 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();
        } 
    }
}

Voici ce que j'obtiens lorsque j'exécute le programme,

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

Comme vous pouvez le voir, SSLContext et hostnameVerifier sont appelés. HostnameVerifier n'est impliqué que lorsque le nom d'hôte ne correspond pas au certificat. J'ai utilisé "www.verisign.net" pour déclencher cela.

30
ZZ Coder

Essayez la bibliothèque Apache Commons HttpClient au lieu d'essayer de rouler la vôtre: http://hc.Apache.org/httpclient-3.x/index.html

De leur exemple de code:

  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
user187702