Pour rappel, voici un exemple d'authentification NTLM :
Client -> Server POST ...
Server -> Client 401 Unauthorized
WWW-Authenticate: NTLM
Client -> Server POST ...
Authorization: NTLM TlRMTVNTUAABAAAAA7IAAAoACgApAAAACQAJACAAAABMSUdIVENJVFlVUlNBLU1JTk9S
Server -> Client 401 Unauthorized
WWW-Authenticate: NTLM TlRMTVNTUAACAAAAAAAAACgAAAABggAAU3J2Tm9uY2UAAAAAAAAAAA==
Client -> Server POST ...
Authorization: NTLM TlRMTVNTUAADAAAAGAAYAHIAAAAYABgAigAAABQAFABAAAAADAAMAFQAAAASABIAYAAAAAAAAACiAAAAAYIAAFUAUgBTAEEALQBNAEkATgBPAFIAWgBhAHAAaABvAGQATABJAEcASABUAEMASQBUAFkArYfKbe/jRoW5xDxHeoxC1gBmfWiS5+iX4OAN4xBKG/IFPwfH3agtPEia6YnhsADT
Server -> Client 200 Ok
On remarque qu'on envoie deux fois le contenu de la requête.La première fois, évidement le server rejete le contenu.
Ce qui est important dans le code ci-dessous c'est :
1 - définir les crédential
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("", "", null, ""));
2 - bien définir les paramètres de connexion et en particulier ne pas avoir l'entête Expect:
.setExpectContinueEnabled(false)
3 - fournir un entity repétable. En effet, la première fois, le contenu de la requête est envoyée, mais le serveur répond un 401
HttpEntity reqEntity = new FileEntity(new File(xmlFile2Send));
Code complet :
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Arrays;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.NTCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.entity.FileEntity;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
/**
* Test NTLM with Apache HttpClient 4.3.x.
*
* HttpClient have pool, becarefull. NTLM is statefull. When TCP connection is avaible, no authentification are requiere.
*
* Add JVM argument to show all http request debug :
* -Dorg.apache.commons.logging.Log=org.apache.commons.logging.impl.SimpleLog
* -Dorg.apache.commons.logging.simplelog.showdatetime=true
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http.impl.conn=DEBUG
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http.impl.client=DEBUG
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http.client=DEBUG
* -Dorg.apache.commons.logging.simplelog.log.org.apache.http=DEBUG
*/
public class TestNtlmSoap {
public static void main(String[] args) throws ClientProtocolException, IOException {
String SOAPUrl = "http://mywebservice.com/ws";
String xmlFile2Send = "file_to_send";
String SOAPAction = "http://tempuri.org/echo";
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(AuthScope.ANY,
new NTCredentials("", "", null, ""));
// Create global request configuration
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setRedirectsEnabled(true)
.setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM))
// Remove Http Header Expect: 100-continue
// http://stackoverflow.com/questions/14459704/does-empty-expect-header-mean-anything
.setExpectContinueEnabled(false)
.build();
CloseableHttpClient httpclient = HttpClients.custom()
.setDefaultCredentialsProvider(credsProvider)
.setDefaultRequestConfig(defaultRequestConfig)
.build();
try {
HttpPost httppost = new HttpPost(SOAPUrl);
httppost.addHeader("Content-Type","text/xml; charset=utf-8");
httppost.addHeader("SOAPAction", SOAPAction);
// Read request file.
// WARNING : must be repeatable ! At fist connection, content send, then authentification done, finaly resend file
HttpEntity reqEntity = new FileEntity(new File(xmlFile2Send));
httppost.setEntity(reqEntity);
System.out.println("executing request " + httppost.getRequestLine());
HttpResponse response = httpclient.execute(httppost);
HttpEntity resEntity = response.getEntity();
System.out.println("----------------------------------------");
System.out.println(response.getStatusLine());
if (resEntity != null) {
System.out.println("Response content length: " + resEntity.getContentLength());
System.out.println("Chunked?: " + resEntity.isChunked());
}
if (resEntity != null && resEntity.isStreaming()) {
InputStream instream = resEntity.getContent();
if (instream != null) {
InputStreamReader isr =
new InputStreamReader(instream);
BufferedReader in = new BufferedReader(isr);
String inputLine;
while ((inputLine = in.readLine()) != null)
System.out.println(inputLine);
instream.close();
}
}
} finally {
// When HttpClient instance is no longer needed,
// shut down the connection to ensure
// immediate deallocation of all system resources
httpclient.close();
}
}
}
Attention, toutefois, il ne faut surtout pas activer l'authentification preemptive, le composant Http Client envoye un authentification basic.
L'authentification preemptive consiste à envoyer les credentials avant que le serveur ne les demande.
Attention aussi, dans l'exemple, il n'y a pas de configuration du pool manager. Il faut savoir que NTLM identifie au niveau de la connexion TCP, donc s'il y a réutilisation de la connexion, il n'y aura pas d'authentification puisque la connexion TCP est déjà identifié. Du coup, il peut y avoir un mélange de connexion.