mardi 2 septembre 2014

Personnaliser la couche transport http avec Apache CXF 2.7.11

Cxf permet de personnaliser le transpoport http. Quel intérêt ?
En relation avec l'utilisation de Apache HttpClient pour l'authentification NTLM par exemple.

Dans un contexte Spring, rien de plus facile, il faut dans la configuration Spring :
<bean class="org.test.cxf.transport.MyHttpTransportFactory" lazy-init="false">
  <property name="transportIds">
    <list>
      <value>http://cxf.apache.org/transports/http</value>
      <value>http://cxf.apache.org/transports/http/configuration</value>
      <value>http://cxf.apache.org/transports/https</value>
      <value>http://cxf.apache.org/transports/https/configuration</value>
      <value>http://schemas.xmlsoap.org/wsdl/http</value>
      <value>http://schemas.xmlsoap.org/wsdl/http/</value>
      <value>http://schemas.xmlsoap.org/soap/http</value>
    </list>
  </property>
</bean>

Attention, redéfinir le HttpTransportFactory pour toutes les valeurs sans quoi sous aurez des surprises.

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.

mardi 22 juillet 2014

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

Depuis Java version 1.6, la JVM support nativement l'authentification NTLM via la classe HttpUrlConnection.
Pour se faire, rien de plus facile.
Il est nécessaire de créer une classe contenant le login, le mot de passe et le domaine :
import java.net.Authenticator;
import java.net.PasswordAuthentication;

/**
 * Class to store login, password and NT domain
 */
public class MyAuthenticator extends Authenticator {
    public MyAuthenticator() {
        super();
    }

    @Override
    public PasswordAuthentication getPasswordAuthentication() {
        return new PasswordAuthentication("MYNT-DOMAIN\\MY_USER", "Password".toCharArray());
    }
}
Enfin, il faut indiquer à la JVM notre classe d'authentification :
// Set authentication
Authenticator.setDefault(new MyAuthenticator());
A présent, nous pouvons lancer notre requête (exemple avec un appel de WebService) :
// Set authentication
Authenticator.setDefault(new MyAuthenticator());

String SOAPUrl = "http://mywebservice.com/ws";
String xmlFile2Send = "file_to_send";
String SOAPAction = "http://tempuri.org/echo";
        
// Create the connection where we're going to send the file.
URL url = new URL(SOAPUrl);
URLConnection connection = url.openConnection();
HttpURLConnection httpConn = (HttpURLConnection) connection;        

// Open the input file. After we copy it to a byte array, we can see
// how big it is so that we can set the HTTP Cotnent-Length
// property.
FileInputStream fin = new FileInputStream(xmlFile2Send);
byte[] b = IOUtils.toByteArray(fin); // Apache commons-io

// Set the appropriate HTTP parameters.
httpConn.setRequestProperty( "Content-Length",
                             String.valueOf( b.length ) );
httpConn.setRequestProperty("Content-Type","text/xml; charset=utf-8");
  httpConn.setRequestProperty("SOAPAction",SOAPAction);
httpConn.setRequestMethod( "POST" );
httpConn.setDoOutput(true);
httpConn.setDoInput(true);

System.out.println("Send request >>>");

// Everything's set up; send the XML that was read in to b.
OutputStream out = httpConn.getOutputStream();
out.write( b );    
out.close();

System.out.println("Read response <<<");

// Requiered to lauch NTLM authentification
Map<String, List<String>> headerFields = httpConn.getHeaderFields();

// Read the response and write it to standard out.

InputStreamReader isr =
    new InputStreamReader(httpConn.getInputStream());
BufferedReader in = new BufferedReader(isr);

String inputLine;

while ((inputLine = in.readLine()) != null)
    System.out.println(inputLine);

in.close();
Il y a deux choses importantes dans l'exemple ci-dessus.
La première c'est de donner la classe contenant les crédentials, la deuxième c'est de lire les headers.
En effet, c'est le httpConn.getHeaderFields() qui va déclencher en interne l'authentification NTLM par la JVM.
Pourquoi ?
Parce que lorsque la classe HttpUrlConnection lit les header, elle vérifie la présence de l'entête WWW-Authenticate et s'assure que le type d'authentification demandée est supportée. Si c'est le cas, la JVM s'en charge avant de rendre la main.

Il y a tout de même un soucis que vous aurez peut-être remarqué, la classe d'authentification se passe à une méthode statique.
De plus, il faut savoir que la JVM à un cache host+url pour chaque autentification. De ce fait, sans couper la JVM, impossible de changer de credentials.
Pour rappel, l'authentification NTLM est un protocole négocié.

Information sur le NTLM :

samedi 5 avril 2014

Faire référence à des variables système dans un fichier de propriétés

Lorsqu'un fichier de propriété (xxx.properties) est lu, il peut être intéressant de faire référence à une variable d'environnement.
Par exemple, un répertoire de base contenant des fichiers :
logDir=${sys:HOME}/home
Par défaut, évidement, ce n'est pas possible.
La mise en oeuvre est très simple, comme le montre l'exemple suivant en dérivant de la classe java.util.Properties :
package org.emeric.property;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.InvalidPropertiesFormatException;
import java.util.Map.Entry;
import java.util.Properties;

/**
 * Extends properties to add support ${sys:xxx} notation.
 * 
 * @author emeric_martineau
 */
public class ExtendProperties extends Properties {

    /**
     * Serial number.
     */
    private static final long serialVersionUID = -123874838055277825L;

    /**
     * Open tag to replace.
     */
    private static final String OPEN_TAG = "${sys:";
    
    /**
     * Open tag length.
     */
    private static final int OPEN_TAG_LEN = OPEN_TAG.length();
    
    /**
     * Close tag to replace.
     */
    private static final String END_TAG = "}";
    
    /**
     * Create empry properties.
     */
    public ExtendProperties() {
        super();
    }
    
    /**
     * Create properties.
     * 
     * @param defaults defautl value/
     */
    public ExtendProperties(final Properties defaults) {
        super();

        // Copy properties
        for(Entry entry : defaults.entrySet()) {
            setProperty((String) entry.getKey(), (String) entry.getValue());
        }
        
        loadExternalProperties();
    }    
    
    @Override
    public synchronized void load(InputStream inStream) throws IOException {
        super.load(inStream);
        
        loadExternalProperties();
    }
    
    @Override
    public synchronized void loadFromXML(InputStream in) throws IOException,
            InvalidPropertiesFormatException {
        super.loadFromXML(in);
        
        loadExternalProperties();
    }
    
    @Override
    public synchronized void load(Reader reader) throws IOException {
        super.load(reader);
        
        loadExternalProperties();
    }
    
    /**
     * Replace external properties reference (${sys:}). 
     */
    private void loadExternalProperties() {
        StringBuilder sb = new StringBuilder();
        
        for(Entry entry : entrySet()) {
            // Clear StringBuilder
            sb.delete(0, sb.length());
            
            replaceValue(entry, sb);
        }
    }
    
    /**
     * Replace value if necessary.
     * 
     * @param entry entry.
     * @param sb string builder
     */
    private void replaceValue(final Entry entry,
            final StringBuilder sb) {
        // Value to search and replace
        String value = (String) entry.getValue();
        // Start tag index
        int startTag = value.indexOf(OPEN_TAG);
        // End tag
        int endTag;
        // Star variable name
        int startVar;
        // Offset
        int offsetTag = 0;
        
        // System var name
        String varName;
        // If value modified
        boolean modified = startTag != -1;        
        
        while (startTag != -1) {
            startVar = startTag + OPEN_TAG_LEN;
            endTag = value.indexOf(END_TAG, startVar);
            
            // Get var name
            varName = value.substring(startVar, endTag);
            
            // Copy value before startTag
            sb.append(value.substring(offsetTag, startTag));
            // Add tag replacement
            sb.append(System.getProperty(varName));
            
            // New start of search
            offsetTag = endTag + 1;
            // Search new tag
            startTag = value.indexOf(OPEN_TAG, offsetTag);
        }
        
        // Add end of string
        sb.append(value.substring(offsetTag)) ;
        
        if (modified) {
            // Update value
            setProperty((String) entry.getKey(), sb.toString());
        }
    }
}

Il serait intéressant maintenant de pouvoir référence une propriété définit dans le fichier de properties.
Ce sera le sujet d'un prochain article.

samedi 15 mars 2014

Afficher le classpath

Lorsqu'une erreur NoClassDefFoundError, il peut être utile de voir le classpath utilisé et afficher la stratégie de recherche.
Pour ce faire, rien de plus simple, il suffit d'ajouter au lancement de la JVM l'option -verbose:class.
Dans ce cas, voilà la sortie obtenu :
[Loaded java.lang.Object from shared objects file]
[Loaded java.io.Serializable from shared objects file]
[Loaded java.lang.Comparable from shared objects file]
[Loaded java.lang.CharSequence from shared objects file]
[Loaded java.lang.String from shared objects file]
... 260 lines deleted ...
[Loaded sun.misc.URLClassPath from shared objects file]
[Loaded sun.net.www.protocol.jar.Handler from shared objects file]
[Loaded sun.misc.Launcher$AppClassLoader from shared objects file]
[Loaded sun.misc.Launcher$AppClassLoader$1 from shared objects file]
[Loaded java.lang.SystemClassLoaderAction from shared objects file]

Comme vous allez vous en apperçevoir, il y aura beaucoup de lignes à lire, mais avec un peu de patience on y arrive.

Pour l'appliquer dans le cadre d'un serveur d'application, c'est dans les options de la JVM du démarrage du serveur qu'il faudra mettre l'option.

vendredi 21 février 2014

Les VarInts

Si vous êtes ammené à utiliser ou étudier le protocole Protobuf de Google, vous y découvrirez un élément particulièrement intéressant, les VarInts.

Ce format de stockage peut être intéressant à mettre en oeuvre. Il est simple et présente des avantages en terme de volumétrie.
Imaginons que les valeurs suivantes pour un même champs et leur équivalent binaire:
  • 50 : 0110010
  • 322 : 00000001 11000010
  • 70000 : 00000001 00010001 01110000
On constate que pour un même champs, les valeurs prennent des tailles différentes.
Si l'on souhaite stocker jusquà la valeur 70000, il faut alors 24 bits.

Admettons que majoritairement, les valeurs du champs soient des nombres entre 0 et 65535, soit entre 1 et 2 octets, avec quelques valeurs sur 3 octets.
Il est dommage de perdre des octets au niveau place, surtout si comme pour une trame réseau, cela devenir gênant au niveau volume.

L'idée des VarInts est la même que UTF-8.
Le bit de poid fort (le 8ème bit) va indiqué s'il le nombre est stocké aussi sur l'octet suivant. Exemple :
Pour la valeur 50, la valeur binaire est 0 110010. L'octet de poid fort est à zéro, donc, le chiffre est stocké sur 1 octet.
Pour la valeur 322, soit 0000000101000010 en binaire, nous alons découper la valeur binaire en 7 bits :
0000000101000010
----------------
00
0000010
1000010
Une fois cette opération fait, est ajouter 1 ou 0 suivant si il y a une suite. Dans l'exemple, cela donne :
00
00000010
11000010
Ce qui donne une valeur fausse de 706.
Le traitement est identique la valeur 70000.

Il n'est pas possible de stocker les valeurs comme ça sur un disque, car il n'est pas possible en l'état de traiter correctement les valeurs.
Il est nécesaire d'inverser l'ordre des octets. Ainsi, sur disque sera écrit la valeur 11000010 puis la valeur 00000010.
De ce fait, une lecture séquentielle est possible. Voici l'algorithme :
Lire le premier octet.
Si bit de poids fort est 1
  Mettre bit de poid fort à 0
  Lecture deuxième octet
  Initialiser un nombre 16 bits à 0
  Mettre le deuxième octets dedans et décaler 7 bits vers la gauche
  Faire un ou logique avec le premier octet modifié
Fin Si
Le traitement des nombres signés est identique.
L'inconvénient majeur qui apparait, c'est le temps de traitement, plus coûteux qu'une simple lecture.
Deuxième inconvénient, c'est que le nombre 255, au lieu d'être enrigistré sur 1 octet, le sera sur 2.
La mise en place des VarInts est donc un choix à étudier précisément suivant les cas.