mardi 19 août 2014

Connexion à un serveur Http en utilisant une authentification NTLM [3/3]

Maintenant que nous savons qu'il n'est pas possible de faire du mutli-user avec une authentification NTLM en environnement mutli-thread, nativement avec la JVM, nous allons utiliser le framework Apache Http Client.

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.

mardi 5 août 2014

Connexion à un serveur Http en utilisant une authentification NTLM [2/3]

Comme vu dans l'article précédent, la JVM stocke les credentials en cache. La question est comment vider ce cache ?
La réponse : ce n'est pas prévu.

Après des recherches sur internet : On trouve la solution dans le dernier lien :
AuthCacheValue.setAuthCache(new AuthCacheImpl());
Mais il y a encore un autre soucis, ce n'est pas thread safe. C'est a dire quedans un environnement multi-thread, avec accès concurrent, le résultat est aléatoire.
Il va donc falloir trouver une autre solution.
Cette solution sera le framework Apache Http Client qui sera traiter dans le prochaine billet.