juil 22
[Java] HttpClient 4 et authentification HTTP
Tavailler avec différentes API permet souvent de faire des découvertes assez intéressantes. Celle d’aujourd’hui concerne l’authentification HTTP en utilisant la librairie HttpClient v4.0.1.
Pour appeler un webservice qui génère un rapport, on m’a donné une simple url :
http://user:pass@domain.com/get_report
Le nom d’utilisateur et le mot de passe comme ça, j’avoue que c’est pas ce que j’aurai fait mais étant donné que c’est pas un webservice interne, j’ai pas le choix. Un petit bout de code accompagnait cette url :
<?php
echo file_get_contents('http://user:pass@domain.com/get_report');
?>
Je teste et ça fonctionne correctement. Etant donné que mon outil est en Java et vu la facilité du truc, la mirgration depuis PHP ne risque, à première vue, pas poser trop trop de problèmes vu que j’ai déjà pas mal bossé avec la librairie HttpClient. Je m’attèle donc à la création d’une classe toute bête pour tester rapidement :
package net.delistage.httpauth;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
public class HttpAuthTest1 {
private static final String URL = "http://user:pass@domain.com/get_report";
/**
* We simply get the report from URL.
*
* @param args
*/
public static void main(String[] args) {
try {
DefaultHttpClient client = new DefaultHttpClient();
// Our request method
HttpGet httpGet = new HttpGet(HttpAuthTest1.URL);
// Try to get the report
HttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
String content = IOUtils.toString(entity.getContent());
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Comme vous pouvez le constater, rien de bien compliqué ou exotique : c’est basique à souhait. Et là bien sûr… ça ne fonctionne pas. Le contenu récupéré n’est pas le rapport mais le formulaire de login au webservice (parce que oui, il dispose d’un accès web en plus de l’API). Je ne me décourage pas et je tente d’envoyer le nom d’utilisateur et de mot de passe plus proprement en utilisant le CredentialsProvider :
package net.delistage.httpauth;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
public class HttpAuthTest2 {
private static final String URL = "http://domain.com/get_report";
private static final String USER = "user";
private static final String PASS = "pass";
/**
* We simply get the report from URL after being authenticated using simple
* credentials.
*
* @param args
*/
public static void main(String[] args) {
try {
DefaultHttpClient client = new DefaultHttpClient();
// Our request method
HttpGet httpGet = new HttpGet(HttpAuthTest2.URL);
// Set credentials
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(
HttpAuthTest2.USER, HttpAuthTest2.PASS);
client.getCredentialsProvider()
.setCredentials(AuthScope.ANY, creds);
// Try to get the report again
HttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
String content = IOUtils.toString(entity.getContent());
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}
}
C’est déjà mieux en théorie. Je re-tente et là bingo : ça ne fonctionne toujours pas. Ca serait trop simple non ? Bon réfléchissons à la cause du problème : on dirait que le serveur tente de faire la requête sur la page avant de se connecter avec les identifiants que je lui fourni. Direction la documentation de la librairie concernant l’authentication HTTP et plus particulièrement le paragraphe sur l’authentification préventive. Pour cela, il faut utiliser une classe implémentant l’interface HttpRequestInterceptor. Le code d’exemple est assez clair mais plutôt que de l’implémenter dans une méthode, je préfère le faire dans une classe dédiée :
package net.delistage.httpauth;
import java.io.IOException;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
public class BasicAuthRequestInterceptor implements HttpRequestInterceptor {
public void process(final HttpRequest request, final HttpContext context)
throws HttpException, IOException {
AuthState authState = (AuthState) context
.getAttribute(ClientContext.TARGET_AUTH_STATE);
CredentialsProvider credsProvider = (CredentialsProvider) context
.getAttribute(ClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context
.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
// If not auth scheme has been initialized yet
if (authState.getAuthScheme() == null) {
AuthScope authScope = new AuthScope(targetHost.getHostName(),
targetHost.getPort());
// Obtain credentials matching the target host
Credentials creds = credsProvider.getCredentials(authScope);
// If found, generate BasicScheme preemptively
if (creds != null) {
authState.setAuthScheme(new BasicScheme());
authState.setCredentials(creds);
}
}
}
}
Il reste maintenant à l’utiliser dans ma classe et voir comment ça se comporte :
package net.delistage.httpauth;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
public class HttpAuthFinal {
private static final String URL = "http://domain.com/get_report";
private static final String USER = "user";
private static final String PASS = "pass";
/**
* We simply get the report from URL after being authenticated.
*
* @param args
*/
public static void main(String[] args) {
try {
DefaultHttpClient client = new DefaultHttpClient();
// Simple interceptor set as first interceptor in the protocol chain
HttpRequestInterceptor preemptiveAuth = new BasicAuthRequestInterceptor();
client.addRequestInterceptor(preemptiveAuth, 0);
// Our request method
HttpGet httpGet = new HttpGet(HttpAuthFinal.URL);
// Set credentials
UsernamePasswordCredentials creds = new UsernamePasswordCredentials(
HttpAuthFinal.USER, HttpAuthFinal.PASS);
client.getCredentialsProvider()
.setCredentials(AuthScope.ANY, creds);
// We can get the report now !
HttpResponse response = client.execute(httpGet);
HttpEntity entity = response.getEntity();
String content = IOUtils.toString(entity.getContent());
System.out.println(content);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Et cette fois, le test est concluant : mon rapport s’affiche bien correctement ! Par contre il reste un problème annoncé par la documentation : utiliser un intercepteur est un risque de sécurité car il est susceptible d’envoyer en clair les identifiants de connexion à des tiers non authorisés. Dans la mesure où je n’ai pas encore trouvé mieux ou une autre solution pour que ça fonctionne, je vais devoir faire avec malgré cela. Si des lecteurs passent par là et ont une autre solution ou idée, je suis tout ouïe.
Articles sur le même sujet :
