Comment tester l'authentification HTTP avec Apache HttpClient
Publié le 16 Août 2013
Apache fournit une lib HttpClient très simple et qui semble assez fréquemment utilisée. C'est d'ailleurs le cas sur mon projet où elle est utilisée pour interagir avec d'autres services webs.
Pour interroger ces services webs nous utilisons simplement l'authentification HTTP basique soit un login/mot de passe.
Le problème est que par défaut HttpClient n'envoie pas l'authentification dans la requête (pour des raisons de sécurité) ce qui donne le callflow suivant:
Client Server
| ----- HTTP req sans authentification ----> |
| <---- HTTP 401 Authentication required ----- |
| ----- HTTP req avec authentication ----> |
| <---- HTTP 200 OK ----- |
Evidemment ça n'est pas optimum et on peut facilement envoyer directement l'authentication dans la première requête comme expiqué par Apache (Preemptive authentication): http://hc.apache.org/httpcomponents-client-ga/tutorial/html/authentication.html
Maintenant voyons comment tester tout ça. Première chose il nous faut un serveur HTTP, ça tombe bien il y en a un dans le JDK (package com.sun.net.httpserver):
HttpServer server = HttpServer.create(new InetSocketAddress(8765), 0);
HttpHandler handler = new HttpHandler() {
@Override
public void handle(HttpExchange httpExchange) throws IOException {
String response = "This is OK";
httpExchange.sendResponseHeaders(200, response.length());
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
};
HttpContext context = server.createContext("/test", handler);
context.setAuthenticator(new BasicHttpAuthenticator("realm-test") {
@Override
public boolean checkCredentials(String authUser, String authPwd) {
return "bliz".equals(authUser) && "1234".equals(authPwd);
}
});
server.setExecutor(null);
server.start();
Voilà le serveur est prêt pour notre test. Nous avons:
- un server HTTP
- un handler qui sert le chemin "/test"
- un authenticator qui vérifie les login / mot de passe
C'est pas trop mal mais il va falloir compter les requêtes envoyer au serveur pour vérifier qu'on est bien en mode preemptive et qu'on ne reçoit pas un 401 à chaque fois.
On peut donc imaginer introduire un compteur de requête au niveau du HttpHandler et de l'incrémenter dans la méthode handle().
Oui mais voilà comme il y a un Autenticator associé au context la réponse 401 est renvoyé automatiquement par le serveur sans qu'on s'en aperçoive au niveau du handler.
Il nous faut donc gérer l'authentification directement dans le handler sans l'associer au context. Ce qui donne au niveau du handler:
@Override
public void handle(HttpExchange httpExchange) throws IOException {
requests++;
String response = "";
if (checkAuth(httpExchange)) {
response = "This is OK";
httpExchange.sendResponseHeaders(200, response.length());
} else {
response = "Missing or wrong authentication";
httpExchange.getResponseHeaders().set("WWW-Authenticate", "Basic realm=realm-test");
httpExchange.sendResponseHeaders(401, response.length());
}
OutputStream os = httpExchange.getResponseBody();
os.write(response.getBytes());
os.close();
}
boolean checkAuth(HttpExchange httpExchange) {
Authenticator.Result res = auth.authenticate(httpExchange);
return res instanceof Authenticator.Success;
}
Ne pas oublier d'enlever la ligne pour ne pas associer d'authenticator au context:
context.setAuthenticator(...)
et de créer l'authenticator au sein du HttpHandler.
Maintenant on voit passer les 2 requêtes dans notre handler et on peut vérifier facilement les requêtes envoyées.