107 votes

Ignorer le certificat SSL dans Apache HttpClient 4.3

Comment ignorer un certificat SSL (trust all) pour Apache HttpClient 4.3 ?

Toutes les réponses que j'ai trouvées sur SO traitent des versions précédentes, et l'API a changé.

En rapport :

Edita:

  • C'est uniquement à des fins de test. Les enfants, ne l'essayez pas chez vous (ou en production).

151voto

mavroprovato Points 2278

Le code ci-dessous fonctionne pour faire confiance aux certificats auto-signés. Vous devez utiliser l'option Stratégie TrustSelfSigned lors de la création de votre client :

SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        builder.build());
CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
        sslsf).build();

HttpGet httpGet = new HttpGet("https://some-server");
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
    System.out.println(response.getStatusLine());
    HttpEntity entity = response.getEntity();
    EntityUtils.consume(entity);
} finally {
    response.close();
}

Je n'ai pas inclus le SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER à dessein : Le but était de permettre des tests avec des certificats auto-signés afin que vous n'ayez pas à acquérir un certificat approprié auprès d'une autorité de certification. Vous pouvez facilement créer un certificat auto-signé avec le nom d'hôte correct, donc faites-le au lieu d'ajouter l'élément SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER drapeau.

8 votes

J'ai dû ajouter l'argument SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER au constructeur pour que cela fonctionne avec le HttpClientBuilder (comme mentionné dans la réponse de holmis83 à vasekt).

0 votes

Consultez également l'exemple sur le site httpclient hc.apache.org/httpcomponents-client-4.3.x/httpclient/exemples/

2 votes

J'ai également dû utiliser le ALLOW_ALL_HOSTNAME_VERIFIER : SSLConnectionSocketFactory(builder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER) ;

94voto

vasekt Points 342

Si vous utilisez PoolingHttpClientConnectionManager, la procédure ci-dessus ne fonctionne pas, le SSLContext personnalisé est ignoré. Vous devez passer socketFactoryRegistry dans le constructeur lors de la création de PoolingHttpClientConnectionManager.

SSLContextBuilder builder = SSLContexts.custom();
builder.loadTrustMaterial(null, new TrustStrategy() {
    @Override
    public boolean isTrusted(X509Certificate[] chain, String authType)
            throws CertificateException {
        return true;
    }
});
SSLContext sslContext = builder.build();
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
        sslContext, new X509HostnameVerifier() {
            @Override
            public void verify(String host, SSLSocket ssl)
                    throws IOException {
            }

            @Override
            public void verify(String host, X509Certificate cert)
                    throws SSLException {
            }

            @Override
            public void verify(String host, String[] cns,
                    String[] subjectAlts) throws SSLException {
            }

            @Override
            public boolean verify(String s, SSLSession sslSession) {
                return true;
            }
        });

Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder
        .<ConnectionSocketFactory> create().register("https", sslsf)
        .build();

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(
        socketFactoryRegistry);
CloseableHttpClient httpclient = HttpClients.custom()
        .setConnectionManager(cm).build();

11 votes

Au lieu de créer votre propre X509HostnameVerifier, vous pouvez utiliser SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER.

0 votes

Comme indiqué ci-dessous par @rich95, le défaut des HttpClients est de vous donner un PoolingHttpClient, ce qui est donc très souvent pertinent. J'ai dû essayer plusieurs de ces réponses avant de découvrir que j'en avais besoin.

1 votes

J'ai essayé de l'appliquer sur WebSphere et j'ai obtenu "java.security.KeyStoreException : IBMTrustManager : Problem accessing trust store java.io.IOException : Invalid keystore format " Pour l'éviter, vous devez passer KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()) ; au lieu de null à builder.loadTrustMaterial

36voto

eis Points 14687

En complément de la réponse de @mavroprovato, si vous voulez faire confiance à tous les certificats au lieu de seulement les auto-signés, vous feriez (dans le style de votre code)

builder.loadTrustMaterial(null, new TrustStrategy(){
    public boolean isTrusted(X509Certificate[] chain, String authType)
        throws CertificateException {
        return true;
    }
});

ou (copier-coller direct de mon propre code) :

import javax.net.ssl.SSLContext;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.ssl.SSLContexts;

// ...

        SSLContext sslContext = SSLContexts
                .custom()
                //FIXME to contain real trust store
                .loadTrustMaterial(new TrustStrategy() {
                    @Override
                    public boolean isTrusted(X509Certificate[] chain,
                        String authType) throws CertificateException {
                        return true;
                    }
                })
                .build();

Et si vous voulez également éviter la vérification du nom d'hôte, vous devez définir

    CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
            sslsf).setSSLHostnameVerifier( NoopHostnameVerifier.INSTANCE).build();

également. (ALLOW_ALL_HOSTNAME_VERIFIER est déprécié).

Avertissement obligatoire : vous ne devriez pas vraiment faire cela, accepter tous les certificats est une mauvaise chose. Cependant, il y a quelques rares cas d'utilisation où vous voulez le faire.

En guise de remarque au code donné précédemment, vous voudrez fermer la réponse même si httpclient.execute() lève une exception

CloseableHttpResponse response = null;
try {
    response = httpclient.execute(httpGet);
    System.out.println(response.getStatusLine());
    HttpEntity entity = response.getEntity();
    EntityUtils.consume(entity);
}
finally {
    if (response != null) {
        response.close();
    }
}

Le code ci-dessus a été testé en utilisant

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.3</version>
</dependency>

Et pour les intéressés, voici mon jeu de test complet :

import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.TrustStrategy;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

public class TrustAllCertificatesTest {
    final String expiredCertSite = "https://expired.badssl.com/";
    final String selfSignedCertSite = "https://self-signed.badssl.com/";
    final String wrongHostCertSite = "https://wrong.host.badssl.com/";

    static final TrustStrategy trustSelfSignedStrategy = new TrustSelfSignedStrategy();
    static final TrustStrategy trustAllStrategy = new TrustStrategy(){
        public boolean isTrusted(X509Certificate[] chain, String authType)
                throws CertificateException {
            return true;
        }
    };

    @Test
    public void testSelfSignedOnSelfSignedUsingCode() throws Exception {
        doGet(selfSignedCertSite, trustSelfSignedStrategy);
    }
    @Test(expected = SSLHandshakeException.class)
    public void testExpiredOnSelfSignedUsingCode() throws Exception {
        doGet(expiredCertSite, trustSelfSignedStrategy);
    }
    @Test(expected = SSLPeerUnverifiedException.class)
    public void testWrongHostOnSelfSignedUsingCode() throws Exception {
        doGet(wrongHostCertSite, trustSelfSignedStrategy);
    }

    @Test
    public void testSelfSignedOnTrustAllUsingCode() throws Exception {
        doGet(selfSignedCertSite, trustAllStrategy);
    }
    @Test
    public void testExpiredOnTrustAllUsingCode() throws Exception {
        doGet(expiredCertSite, trustAllStrategy);
    }
    @Test(expected = SSLPeerUnverifiedException.class)
    public void testWrongHostOnTrustAllUsingCode() throws Exception {
        doGet(wrongHostCertSite, trustAllStrategy);
    }

    @Test
    public void testSelfSignedOnAllowAllUsingCode() throws Exception {
        doGet(selfSignedCertSite, trustAllStrategy, NoopHostnameVerifier.INSTANCE);
    }
    @Test
    public void testExpiredOnAllowAllUsingCode() throws Exception {
        doGet(expiredCertSite, trustAllStrategy, NoopHostnameVerifier.INSTANCE);
    }
    @Test
    public void testWrongHostOnAllowAllUsingCode() throws Exception {
        doGet(expiredCertSite, trustAllStrategy, NoopHostnameVerifier.INSTANCE);
    }

    public void doGet(String url, TrustStrategy trustStrategy, HostnameVerifier hostnameVerifier) throws Exception {
        SSLContextBuilder builder = new SSLContextBuilder();
        builder.loadTrustMaterial(trustStrategy);
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                builder.build());
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
                sslsf).setSSLHostnameVerifier(hostnameVerifier).build();

        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = httpclient.execute(httpGet);
        try {
            System.out.println(response.getStatusLine());
            HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    }
    public void doGet(String url, TrustStrategy trustStrategy) throws Exception {

        SSLContextBuilder builder = new SSLContextBuilder();
        builder.loadTrustMaterial(trustStrategy);
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                builder.build());
        CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory(
                sslsf).build();

        HttpGet httpGet = new HttpGet(url);
        CloseableHttpResponse response = httpclient.execute(httpGet);
        try {
            System.out.println(response.getStatusLine());
            HttpEntity entity = response.getEntity();
            EntityUtils.consume(entity);
        } finally {
            response.close();
        }
    }
}

(projet de test de travail dans github )

1 votes

HttpClient#execute ne renverra jamais un objet réponse nul en cas d'exception. De plus, les implémentations de HttpClient de série assureront la désallocation automatique de toutes les ressources du système, telles que les connexions louées, en cas d'exception pendant l'exécution de la requête. La gestion des exceptions utilisée par mavroprovato est parfaitement adéquate.

0 votes

@oleg le point de Interface fermable est de "Fermer [...] le flux et libérer toutes les ressources système qui lui sont associées. Si le flux est déjà fermé, l'invocation de cette méthode n'a aucun effet", c'est donc une bonne pratique de l'utiliser même si elle n'est pas nécessaire. De plus, je ne comprends pas le commentaire sur le retour d'une réponse nulle - bien sûr que non, si elle lève une exception, elle ne retourne rien ?

1 votes

Apache HttpClient jamais jamais renvoie un objet de réponse nul ou partiellement initialisé. Cela n'a rien à voir avec le nombre de fois où #close est invoqué, mais plutôt avec une vérification de nullité complètement inutile dans la clause finally.

23voto

migo Points 51

Un petit complément à la réponse de vasekt :

La solution fournie avec le SocketFactoryRegistry fonctionne lorsqu'on utilise le PoolingHttpClientConnectionManager.

Cependant, les connexions via http ordinaire ne fonctionnent plus. Vous devez ajouter un PlainConnectionSocketFactory pour le protocole http en plus pour qu'elles fonctionnent à nouveau :

Registry<ConnectionSocketFactory> socketFactoryRegistry = 
  RegistryBuilder.<ConnectionSocketFactory> create()
  .register("https", sslsf)
  .register("http", new PlainConnectionSocketFactory()).build();

0 votes

Je crois http utilise le protocole PlainConnectionSocketFactory par défaut. J'ai seulement enregistré https y el httpclient peut toujours obtenir des URL HTTP simples. Je ne pense donc pas que cette étape soit nécessaire.

0 votes

@soulmachine il ne sera pas pour PoolingHttpClientConnectionManager

0 votes

Merci @migo J'avais vraiment besoin des deux, en qa je traite avec http, en production, c'est https

15voto

Saurabh Points 911

Après avoir essayé plusieurs options, la configuration suivante a fonctionné pour http et https :

SSLContextBuilder builder = new SSLContextBuilder();
builder.loadTrustMaterial(null, new TrustSelfSignedStrategy());
SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                builder.build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

Registry<ConnectionSocketFactory> registry = RegistryBuilder. 
                 <ConnectionSocketFactory> create()
                .register("http", new PlainConnectionSocketFactory())
                .register("https", sslsf)
                .build();

PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(registry);
cm.setMaxTotal(2000);

CloseableHttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(sslsf)
                .setConnectionManager(cm)
                .build();

J'utilise http-client 4.3.3 : compile 'org.apache.httpcomponents:httpclient:4.3.3'

1 votes

Merci d'avoir fourni un exemple complet et entièrement fonctionnel ! Je rencontrais de nombreux problèmes avec les solutions précédentes et cet exemple m'a beaucoup aidé. Le fait que vous ayez fourni les déclarations d'importation m'a également aidé, car il y a plusieurs classes avec les mêmes noms, ce qui ajoute à la confusion.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X