web-dev-qa-db-fra.com

Authentification de base HttpClientBuilder

Depuis HttpClient 4.3, j'utilise HttpClientBuilder. Je me connecte à un service REST disposant d'une authentification de base. Je configure les informations d'identification comme suit:

HttpClientBuilder builder = HttpClientBuilder.create();

// Get the client credentials
String username = Config.get(Constants.CONFIG_USERNAME);
String password = Config.get(Constants.CONFIG_PASSWORD);

// If username and password was found, inject the credentials
if (username != null && password != null)
{
    CredentialsProvider provider = new BasicCredentialsProvider();

    // Create the authentication scope
    AuthScope scope = new AuthScope(AuthScope.ANY_Host, AuthScope.ANY_PORT, AuthScope.ANY_REALM);

    // Create credential pair
    UsernamePasswordCredentials credentials = new UsernamePasswordCredentials(username, password);

    // Inject the credentials
    provider.setCredentials(scope, credentials);

    // Set the default credentials provider
    builder.setDefaultCredentialsProvider(provider);
}

Toutefois, cela ne fonctionne pas (le service REST que j’utilise renvoie 401). Qu'est-ce qui ne va pas?

16
Sayak Banerjee

De la Documentation d'authentification préemptive ici:

http://hc.Apache.org/httpcomponents-client-ga/tutorial/html/authentication.html

Par défaut, httpclient ne fournira pas d'informations d'identification à titre préventif, il créera d'abord une requête HTTP sans paramètres d'authentification. Ceci est voulu, par mesure de sécurité et dans le cadre des spécifications. Cependant, cela pose des problèmes si vous ne renouvelez pas la connexion ou si vous vous connectez à, vous vous attendez à ce que vous envoyiez des informations d'authentification lors de la première connexion. Cela entraîne également une latence supplémentaire pour une demande, car vous devez effectuer plusieurs appels et entraîne l'apparition de 401 dans les journaux.

La solution de contournement consiste à utiliser un cache d'authentification pour prétendre que vous vous êtes déjà connecté au serveur une fois. Cela signifie que vous ne ferez qu'un seul appel HTTP et ne verrez pas un 401 dans les journaux: 

CloseableHttpClient httpclient = HttpClientBuilder.create().build();

HttpHost targetHost = new HttpHost("localhost", 80, "http");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(
        new AuthScope(targetHost.getHostName(), targetHost.getPort()),
        new UsernamePasswordCredentials("username", "password"));

// Create AuthCache instance
AuthCache authCache = new BasicAuthCache();
// Generate BASIC scheme object and add it to the local auth cache
BasicScheme basicAuth = new BasicScheme();
authCache.put(targetHost, basicAuth);

// Add AuthCache to the execution context
HttpClientContext context = HttpClientContext.create();
context.setCredentialsProvider(credsProvider);
context.setAuthCache(authCache);

HttpGet httpget = new HttpGet("/");
for (int i = 0; i < 3; i++) {
    CloseableHttpResponse response = httpclient.execute(
            targetHost, httpget, context);
    try {
        HttpEntity entity = response.getEntity();

    } finally {
        response.close();
    }
}

Remarque: vous devez faire confiance à l'hôte auquel vous vous connectez. Si vous utilisez HTTP, votre nom d'utilisateur et votre mot de passe seront envoyés en texte clair (enfin, base64, mais cela ne compte pas). 

Vous devriez également utiliser un Authscope beaucoup plus spécifique plutôt que de vous appuyer sur AuthScope .ANY_Host et AuthScope.ANY_PORT comme dans votre exemple.

35
Cetra

En fait, puisque vous faites déjà confiance au serveur, il est probablement plus simple de construire vous-même l'en-tête d'autorisation.

 byte[] credentials = Base64.encodeBase64((username + ":" + password).getBytes(StandardCharsets.UTF_8));
 request.setHeader("Authorization", "Basic " + new String(credentials, StandardCharsets.UTF_8));
 httpClient.execute(request);

Il ne s'agit que de l'un des cas dans lesquels il est plus facile de lire la spécification et de la lancer vous-même.

14
user2077221

Je viens d'essayer votre exemple de code (par rapport à une URL simple autorisant l'authentification de base) et tout fonctionne correctement. Voici le journal de la HttpClient - un peu simplifié: 

web - 2014-01-04 12:43:19,700 [main] DEBUG o.a.h.c.protocol.RequestAddCookies - CookieSpec selected: best-match
web - 2014-01-04 12:43:19,710 [main] DEBUG o.a.h.c.protocol.RequestAuthCache - Auth cache not set in the context
web - 2014-01-04 12:43:19,728 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Opening connection {}->http://localhost:8080
web - 2014-01-04 12:43:19,730 [main] DEBUG o.a.h.c.HttpClientConnectionManager - Connecting to localhost/127.0.0.1:8080
web - 2014-01-04 12:43:19,731 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,731 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: UNCHALLENGED
web - 2014-01-04 12:43:19,731 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
web - 2014-01-04 12:43:19,732 [main] DEBUG org.Apache.http.headers - http-outgoing-0 >> GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,732 [main] DEBUG org.Apache.http.headers - http-outgoing-0 >> Host: localhost:8080
web - 2014-01-04 12:43:19,732 [main] DEBUG org.Apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.3.1 (Java 1.5)
web - 2014-01-04 12:43:19,735 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << HTTP/1.1 401 Unauthorized
web - 2014-01-04 12:43:19,735 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Server: Apache-Coyote/1.1
web - 2014-01-04 12:43:19,735 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Set-Cookie: JSESSIONID=B8E6D0D7DE0C99991A74E9B2E4EA68AE; Path=/spring-security-mvc-basic-auth/; HttpOnly
web - 2014-01-04 12:43:19,735 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << WWW-Authenticate: Basic realm="Baeldung"
web - 2014-01-04 12:43:19,735 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Content-Length: 75
web - 2014-01-04 12:43:19,735 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Date: Sat, 04 Jan 2014 10:43:19 GMT
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Authentication required
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - localhost:8080 requested authentication
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Authentication schemes in the order of preference: [negotiate, Kerberos, NTLM, Digest, Basic]
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for negotiate authentication scheme not available
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for Kerberos authentication scheme not available
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for NTLM authentication scheme not available
web - 2014-01-04 12:43:19,738 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Challenge for Digest authentication scheme not available
web - 2014-01-04 12:43:19,745 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Selected authentication options: [BASIC]
web - 2014-01-04 12:43:19,746 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Executing request GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,746 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Target auth state: CHALLENGED
web - 2014-01-04 12:43:19,746 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Generating response to an authentication challenge using basic scheme
web - 2014-01-04 12:43:19,747 [main] DEBUG o.a.h.impl.execchain.MainClientExec - Proxy auth state: UNCHALLENGED
web - 2014-01-04 12:43:19,747 [main] DEBUG org.Apache.http.headers - http-outgoing-0 >> GET /spring-security-mvc-basic-auth/homepage.html HTTP/1.1
web - 2014-01-04 12:43:19,747 [main] DEBUG org.Apache.http.headers - http-outgoing-0 >> Host: localhost:8080
web - 2014-01-04 12:43:19,747 [main] DEBUG org.Apache.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.3.1 (Java 1.5)
web - 2014-01-04 12:43:19,747 [main] DEBUG org.Apache.http.headers - http-outgoing-0 >> Authorization: Basic dXNlcjE6dXNlcjFQYXNz
web - 2014-01-04 12:43:19,750 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << HTTP/1.1 200 OK
web - 2014-01-04 12:43:19,750 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Server: Apache-Coyote/1.1
web - 2014-01-04 12:43:19,750 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Set-Cookie: JSESSIONID=C03FD4EB1421A4C3A003ADC895D49599; Path=/spring-security-mvc-basic-auth/; HttpOnly
web - 2014-01-04 12:43:19,750 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Content-Type: text/html;charset=ISO-8859-1
web - 2014-01-04 12:43:19,750 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Content-Language: en-US
web - 2014-01-04 12:43:19,751 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Content-Length: 96
web - 2014-01-04 12:43:19,751 [main] DEBUG org.Apache.http.headers - http-outgoing-0 << Date: Sat, 04 Jan 2014 10:43:19 GMT
web - 2014-01-04 12:43:19,751 [main] DEBUG o.a.http.impl.auth.HttpAuthenticator - Authentication succeeded
web - 2014-01-04 12:43:19,751 [main] DEBUG o.a.h.i.c.TargetAuthenticationStrategy - Caching 'basic' auth scheme for http://localhost:8080
web - 2014-01-04 12:43:19,760 [main] DEBUG o.a.h.c.p.ResponseProcessCookies - Cookie accepted: "[version: 0][name: JSESSIONID][value: C03FD4EB1421A4C3A003ADC895D49599][domain: localhost][path: /spring-security-mvc-basic-auth/][expiry: null]". 

Donc, simplement: 
- le serveur conteste la requête initiale 
- HttpClient reconnaît le schéma d'authentification de base et répond correctement au défi 
- à ce stade, le serveur serveur le 200 OK attendu 

Il se peut que le service REST que vous utilisez n'utilise pas réellement l'authentification de base. Vous pouvez essayer de coller les journaux complets HttpClient pour mieux diagnostiquer le problème. 

J'espère que cela pourra aider. 

2
Eugen

Je pense HttpClient est comme une autre solution basée sur curl, il suit la spécification.

Et la spécification est "N'envoyez pas les informations d'identification sauf si le serveur vous le dit" . Vous obtenez donc un 401 ("Je veux envoyer des informations d'identification") ...

C'est un problème courant dans le savon: quand on ne sait pas, ce n'est pas évident

1
farvilain