Programmation de l'authentification de client

Pour l'authentification, WebSphere eXtreme Scale fournit un environnement d'exécution qui envoie les données d'identification du client vers le serveur. Le produit appelle ensuite le plug-in Authenticator.

Pour pouvoir procéder à l'authentification, WebSphere eXtreme Scale nécessite que vous implémentiez les plug-in suivants :

Plug-in Credential et CredentialGenerator

Lorsqu'un client eXtreme Scale se connecte à un serveur qui exige une authentification, le client est requis de fournir des données d'identification. Les données d'identification du client sont représentées par une interface com.ibm.websphere.objectgrid.security.plugins.Credential. Ces données d'identification peuvent être une paire nom-mot de passe, un ticket Kerberos, un certificat client ou des données au format convenu par le client et le serveur. Cette interface définit explicitement les méthodes equals(Object) et hashCode. Ces deux méthodes sont importantes car les objets Subject authentifiés sont mis en cache par le biais de l'objet Credential en tant que clé sur le serveur. WebSphere eXtreme Scale fournit également un plug-in pour générer des données d'identification. Ce plug-in est représenté par l'interface com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator et est utile lorsque les données d'identification peuvent expirer. Dans ce cas, la méthode getCredential est appelée pour renouveler ces données.

L'interface Credential définit explicitement les méthodes equals(Object) et hashCode. Ces deux méthodes sont importantes car les objets Subject authentifiés sont mis en cache par le biais de l'objet Credential en tant que clé sur le serveur.

Vous pouvez également générer des données d'identification avec le plug-in fourni. Ce plug-in est représenté par l'interface com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator et il est utile lorsque les données d'identification peuvent expirer. Dans ce cas, la méthode getCredential est appelée pour renouveler ces données. Pour plus d'informations, voir Interface CredentialGenerator.

Trois implémentations sont fournies par défaut pour les interfaces Credential :

  • l'implémentation com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredential qui contient une paire ID utilisateur/mot de passe
  • l'implémentation com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredential qui contient des clés d'authentification et d'autorisation spécifiques à WebSphere Application Server. Ces clés peuvent être utilisées pour propager les attributs de sécurité sur les serveurs d'applications appartenant au même domaine de sécurité

WebSphere eXtreme Scale fournit également un plug-in permettant de générer des données d'identification. Ce plug-in est représenté par l'interface com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator. WebSphere eXtreme Scale est livré avec deux implémentations par défaut :

  • com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredentialGenerator accepte un ID utilisateur et un mot de passe. Lorsque la méthode getCredential est appelée, elle retourne un objet UserPasswordCredential qui contient l'ID utilisateur et le mot de passe
  • com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator représente un générateur de données d'identification (jeton de sécurité) lors d'une exécution dans WebSphere Application Server. Lorsque la méthode getCredential est appelée, le Subject associé à l'unité d'exécution en cours est extrait. Puis les informations de sécurité présentes dans cet objet Subject sont converties en objet WSTokenCredential. Vous pouvez spécifier s'il y a lieu d'extraire de l'unité d'exécution un sujet runAs ou un sujet caller à l'aide de la constante WSTokenCredentialGenerator.RUN_AS_SUBJECT ou de la constante WSTokenCredentialGenerator.CALLER_SUBJECT.

UserPasswordCredential et UserPasswordCredentialGenerator

A des fins de test, WebSphere eXtreme Scale fournit les implémentations de plug-in suivantes :

  1. com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredential
  2. com.ibm.websphere.objectgrid.security.plugins.builtins.UserPasswordCredentialGenerator

Les données d'identification utilisateur/mot de passe stockent un ID utilisateur et un mot de passe. Le générateur de ces données d'identification contient alors cet ID utilisateur et ce mot de passe.

L'exemple de code suivant montre comment implémenter ces deux plug-in.

UserPasswordCredential.java
// Cet exemple de programme est fourni TEL QUEL et peut être utilisé, exécuté, copié et modifié
// gratuitement par le client
// (a) à des fins d'études,
// (b) afin de développer des applications conçues pour être exécutées avec un produit IBM WebSphere,
// soit pour un usage interne soit pour une redistribution par le client, en tant que partie de
// l'application, au sein des produits du client.
//   Eléments sous licence - Propriété d'IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007
package com.ibm.websphere.objectgrid.security.plugins.builtins;

import com.ibm.websphere.objectgrid.security.plugins.Credential;

/**
 * Cette classe représente des données d'identification contenant un ID utilisateur et un mot de passe.
 *
 * @ibm-api
 * @since WAS XD 6.0.1
 *
 * @see Credential
 * @see UserPasswordCredentialGenerator#getCredential()
 */
public class UserPasswordCredential implements Credential {

    private static final long serialVersionUID = 1409044825541007228L;

    private String ivUserName;

    private String ivPassword;

    /**
     * Crée un UserPasswordCredential avec le nom d'utilisateur
     * et le mot de passe spécifiés.
     *
     * @param userName Le nom d'utilisateur pour ces données d'identification
     * @param password Le mot de passe pour ces données d'identification
     *
     * @throws IllegalArgumentException si userName or password sont <code>null</code>
     */
    public UserPasswordCredential(String userName, String password) {
        super();
        if (userName == null || password == null) {
            throw new IllegalArgumentException("User name and password cannot be null.");
        }
        this.ivUserName = userName;
        this.ivPassword = password;
    }

    /**
     * Obtient le nom d'utilisateur de ces données d'identification.
     *
     * @return   l'argument username qui a été passé au constructeur
     *           ou la méthode <code>setUserName(String)</code>
     *           de cette classe
     *
     * @see #setUserName(String)
     */
    public String getUserName() {
        return ivUserName;
    }

    /**
     * Définit le nom d'utilisateur pour ces données d'identification.
     *
     * @param userName Le nom d'utilisateur à définir.
     *
     * @throws IllegalArgumentException si userName est <code>null</code>
     */
    public void setUserName(String userName) {
        if (userName == null) {
            throw new IllegalArgumentException("User name cannot be null.");
        }
        this.ivUserName = userName;
    }

    /**
     * Obtention du mot de passe pour ces données d'identification.
     *
     * @return   l'argument password qui a été passé au constructeur
     *           ou la méthode <code>setPassword(String)</code>
     *           de cette classe
     *
     * @see #setPassword(String)
     */
    public String getPassword() {
        return ivPassword;
    }

    /**
     * Définit le mot de passe pour ces données d'identification
     *
     * @param password Le mot de passe à définir.
     *
     * @throws IllegalArgumentException si password est <code>null</code>
     */
    public void setPassword(String password) {
        if (password == null) {
            throw new IllegalArgumentException("Password cannot be null.");
        }
        this.ivPassword = password;
    }

    /**
     * Vérifie l'égalité des deux objets UserPasswordCredential.
     * <p>
     * Deux objets UserPasswordCredential sont égaux si et seulement si leurs noms d'utilisateur
     * et leurs mots de passe sont égaux.
     *
     * @param o l'objet dont nous testons l'égalité avec cet objet.
     *
     * @return <code>true</code> si les deux objets UserPasswordCredential sont équivalents.
     *
     * @see Credential#equals(Object)
     */
    public boolean equals (Object o)  {
        if (this == o) {
            return true;
        }
        if (o instanceof UserPasswordCredential) {
            UserPasswordCredential other = (UserPasswordCredential) o;
            return other.ivPassword.equals(ivPassword) && other.ivUserName.equals(ivUserName);
        }

        return false;
    }

    /**
     * Retourne le code de hachage de l'objet UserPasswordCredential.
     *
     * @return le code de hachage de cet objet
     *
     * @see Credential#hashCode()
     */
    public int hashCode ()  {
        return ivUserName.hashCode() + ivPassword.hashCode();
    }
}
UserPasswordCredentialGenerator.java
// Cet exemple de programme est fourni TEL QUEL et peut être utilisé, exécuté, copié et modifié
// gratuitement par le client
// (a) à des fins d'études,
// (b) afin de développer des applications conçues pour être exécutées avec un produit IBM WebSphere,
// soit pour un usage interne soit pour une redistribution par le client, en tant que partie de
// l'application, au sein des produits du client.
//   Eléments sous licence - Propriété d'IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007
package com.ibm.websphere.objectgrid.security.plugins.builtins;

import java.util.StringTokenizer;

import com.ibm.websphere.objectgrid.security.plugins.Credential;
import com.ibm.websphere.objectgrid.security.plugins.CredentialGenerator;

/**
 * Ce générateur de données d'identification crée des objets <code>UserPasswordCredential</code>.
 * <p>
 * UserPasswordCredentialGenerator a une relation un à un
 * avec UserPasswordCredential car il ne peut créer qu'un UserPasswordCredential
 * représentant une seule identité.
 *
 * @since WAS XD 6.0.1
 * @ibm-api
 *
 * @see CredentialGenerator
 * @see UserPasswordCredential
 */
public class UserPasswordCredentialGenerator implements CredentialGenerator {

    private String ivUser;

    private String ivPwd;

    /**
     * Crée un UserPasswordCredentialGenerator sans nom d'utilisateur ni mot de passe.
     *
     * @see #setProperties(String)
     */
    public UserPasswordCredentialGenerator() {
        super();
    }

    /**
     * Crée un UserPasswordCredentialGenerator avec un nom d'utilisateur et un mot de passe
     * spécifiés
     *
     * @param user the user name
     * @param pwd the password
     */
    public UserPasswordCredentialGenerator(String user, String pwd) {
        ivUser = user;
        ivPwd = pwd;
    }

    /**
     * Crée un nouvel objet <code>UserPasswordCredential</code>
     * avec le nom d'utilisateur et le mot de passe de cet objet.
     *
     * @return une nouvelle instance d'<code>UserPasswordCredential</code>
     *
     * @see CredentialGenerator#getCredential()
     * @see UserPasswordCredential
     */
    public Credential getCredential() {
        return new UserPasswordCredential(ivUser, ivPwd);
    }

    /**
     * Obtient le mot de passe pour ce générateur de données d'identification.
     *
     * @return   l'argument password qui a été passé au constructeur
     */
    public String getPassword() {
        return ivPwd;
    }

    /**
     * Obtient le nom d'utilisateur de ces données d'identification.
     *
     * @return   l'argument user qui a été passé au constructeur
     *           de cette classe
     */
    public String getUserName() {
        return ivUser;
    }
    /**
     * Définit des propriétés supplémentaires, un nom d'utilisateur et un mot de passe
     *
     * @param properties chaîne properties avec un nom d'utilisateur
     *                   et un mot de passe séparés par un espace.
     *
     * @throws IllegalArgumentException si le format n'est pas valide
     */
    public void setProperties(String properties) {
        StringTokenizer token = new StringTokenizer(properties, " ");
        if (token.countTokens() != 2) {
            throw new IllegalArgumentException(
                "Les propriétés doivent comporter un nom d'utilisateur et un mot de passe séparés par un espace.");
        }

        ivUser = token.nextToken();
        ivPwd = token.nextToken();
    }
    /**
     * Vérifie l'égalité des deux objets UserPasswordCredentialGenerator.
     * <p>
     * Deux objets UserPasswordCredentialGenerator sont égaux si et seulement si leurs noms d'utilisateur
     * et leurs mots de passe sont égaux.
     *
     * @param obj the object we are testing for equality with this object.
     *
     * @return <code>true</code> if both UserPasswordCredentialGenerator objects
     *         are equivalent.
     */
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        }

        if (obj != null && obj instanceof UserPasswordCredentialGenerator) {
            UserPasswordCredentialGenerator other = (UserPasswordCredentialGenerator) obj;

            boolean bothUserNull = false;
            boolean bothPwdNull = false;

            if (ivUser == null) {
                if (other.ivUser == null) {
                    bothUserNull = true;
                } else {
                    return false;
                }
            }

            if (ivPwd == null) {
                if (other.ivPwd == null) {
                    bothPwdNull = true;
                } else {
                    return false;
                }
            }

            return (bothUserNull || ivUser.equals(other.ivUser)) && (bothPwdNull || ivPwd.equals(other.ivPwd));
        }

        return false;
    }

    /**
     * Returns the hashcode of the UserPasswordCredentialGenerator object.
     *
     * @return le code de hachage de cet objet
     */
    public int hashCode ()  {

        return ivUser.hashCode() + ivPwd.hashCode();
    }

}

La classe UserPasswordCredential contient deux attributs: userName et password. UserPasswordCredentialGenerator sert de fabrique contenant les objets UserPasswordCredential.

WSTokenCredential et WSTokenCredentialGenerator

Lorsque les clients WebSphere eXtreme Scale sont tous déployés dans WebSphere Application Server, l'application client peut utiliser ces deux implémentations pré-intégrées pour peu que les conditions suivantes soient réunies :

  1. La sécurité globale de WebSphere Application Server est activée.
  2. Tous les clients et les serveurs WebSphere eXtreme Scale s'exécutent sur des machines virtuelles Java WebSphere Application Server.
  3. Les serveurs d'applications se trouvent tous dans le même domaine de sécurité.
  4. Le client est déjà authentifiée dans WebSphere Application Server.

Dans ce cas de figure, le client peut utiliser la classe com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator pour générer des données d'identification. Le serveur utilise une classe d'implémentation WSAuthenticator pour authentifier ces données.

Ce scénario exploite le fait que le client eXtreme Scale a déjà été authentifié. Du fait que les serveurs d'applications sur lesquels sont les serveurs se trouvent dans le même domaine de sécurité que ceux qui hébergent les clients, les jetons de sécurité peuvent être propagés du client vers le serveur de sorte que le même registre d'utilisateurs n'a pas besoin d'être authentifié à nouveau.

Remarque : Ne partez pas du principe qu'un CredentialGenerator génère toujours les mêmes données d'identification. Dans le cas de données pouvant expirer et réactualisables, le CredentialGenerator doit être capable de générer les données d'identification valides les plus récentes pour garantir l'authentification. L'utilisation de tickets Kerberos comme objet Credential en est un bon exemple. Lorsque le ticket Kerberos s'actualise, le CredentialGenerator doit extraire le ticket actualisé lorsque la méthode CredentialGenerator.getCredential est appelée.

Plug-in Authenticator

Après que le client eXtreme Scale a récupéré l'objet Credential par le biais de l'objet CredentialGenerator, cet objet Credential est envoyé en même temps de la requête client au serveur eXtreme Scale. Le serveur authentifie l'objet Credential avant de traiter la demande. Si l'objet Credential est authentifié, un objet Subject est renvoyé pour représenter ce client.

Cet objet Subject est alors mis en cache et expire lorsque de délai d'expiration de la session est atteint. Ce délai peut être défini par le biais de la propriété loginSessionExpirationTime dans le fichier XML du cluster. Par exemple, le paramètre loginSessionExpirationTime="300" entraîne l'expiration de l'objet Subject dans 300 secondes.

Cet objet Subject est alors utilisé pour autorisé la requête, ce qui sera illustré plus loin. Un serveur eXtreme Scale utilise le plug-in Authenticator pour authentifier l'objet Credential. Pour plus de détails, voir Authenticator .

C'est dans le plug-in Authenticator que l'environnement d'exécution d'eXtreme Scale authentifie l'objet Credential issu du registre des utilisateurs clients, un serveur LDAP, par exemple.

WebSphere eXtreme Scale ne fournit pas de configuration de registre d'utilisateurs immédiatement utilisable. Le soin de configurer et de gérer ce registre a volontairement été laissé en dehors de WebSphere eXtreme Scale pour des raisons de simplicité et de flexibilité. Ce plug-in implémente la connexion au registre des utilisateurs et l'authentification auprès de ce registre. Par exemple, une implémentation d'Authenticator extraira des données d'identification l'ID utilisateur et le mot de passe, les utilisera pour la connexion et la validation auprès d'un serveur LDAP et créera un objet Subject résultant de l'authentification. L'implémentation peut utiliser des modules de connexion JAAS. Un objet Subject résultant de l'authentification est renvoyé.

Vous remarquerez que cette méthode crée deux exceptions : InvalidCredentialException et ExpiredCredentialException. L'exception InvalidCredentialException indique que les données d'identification ne sont pas valides. L'exception ExpiredCredentialException signale que les données d'identification ont expiré. Si la méthode authenticate donne lieu à l'une de ces deux exceptions, ces exceptions sont renvoyées au client. Mais l'environnement d'exécution du client gère différemment ces deux exceptions :

  • S'il s'agit d'une exception InvalidCredentialException, l'environnement d'exécution du client affiche cette exception. Votre application doit gérer l'exception. Vous pouvez corriger le CredentialGenerator, par exemple, et retenter l'opération.
  • Si l'erreur est une exception ExpiredCredentialException et que le nombre des nouvelles tentatives est différent de 0, l'environnement d'exécution du client appelle à nouveau la méthode CredentialGenerator.getCredential et envoie au serveur le nouvel objet Credential. Si l'authentification des nouvelles données d'identification réussit, le serveur traite la demande. Si elle échoue, l'exception est renvoyée au client. Si le nombre des tentatives d'authentification atteint la valeur définie et que le client continue à recevoir une exception ExpiredCredentialException, il résulte une exception ExpiredCredentialException exception. Votre application doit gérer l'erreur.

L'interface Authenticator apporte une grande flexibilité. Vous pouvez implémenter cette interface de la manière qui vous est propre. Vous pouvez, par exemple, l'implémenter pour qu'elle prenne en charge deux registres d'utilisateurs différents.

WebSphere eXtreme Scale fournit des exemples d'implémentations du plug-in Authenticator. Hormis celle du plug-in Authenticator de WebSphere Application Server, les autres implémentations ne sont que des exemples fournies à des fins de tests.

KeyStoreLoginAuthenticator

Cet exemple utilise une implémentation pré-intégrée dans eXtreme Scale : KeyStoreLoginAuthenticator, qui n'est là qu'à des fins de test et qu'à titre d'exemple (un fichier de clés est un registre simple d'utilisateurs, qui ne doit pas être utilisé dans le cadre d'un environnement de production). Là aussi, la classe est affichée pour montrer comment implémenter un authentificateur.

KeyStoreLoginAuthenticator.java
// Cet exemple de programme est fourni TEL QUEL et peut être utilisé, exécuté, copié et modifié
// gratuitement par le client
// (a) à des fins d'études,
// (b) afin de développer des applications conçues pour être exécutées avec un produit IBM WebSphere,
// soit pour un usage interne soit pour une redistribution par le client, en tant que partie de
// l'application, au sein des produits du client.
//   Eléments sous licence - Propriété d'IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007

package com.ibm.websphere.objectgrid.security.plugins.builtins;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import com.ibm.websphere.objectgrid.security.plugins.Authenticator;
import com.ibm.websphere.objectgrid.security.plugins.Credential;
import com.ibm.websphere.objectgrid.security.plugins.ExpiredCredentialException;
import com.ibm.websphere.objectgrid.security.plugins.InvalidCredentialException;
import com.ibm.ws.objectgrid.Constants;
import com.ibm.ws.objectgrid.ObjectGridManagerImpl;
import com.ibm.ws.objectgrid.security.auth.callback.UserPasswordCallbackHandlerImpl;

/**
 * Cette classe est une implémentation de l'interface <code>Authenticator</code>
 * lorsqu'un nom d'utilisateur et un mot de passe sont utilisés comme données d'identification.
 * <p>
 * Lorsqu'on utilise l'authentification par ID utilisateur et mot de passe, les données d'identification passées
 * à la méthode <code>authenticate(Credential)</code> est un objet UserPasswordCredential.
 * <p>
 * Cette implémentation va utiliser un <code>KeyStoreLoginModule</code> pour authentifier
 * l'utilisateur dans le magasin de clés à l'aide d'un module de connexion JAAS "KeyStoreLogin". Le magasin de clés
 * peut être configuré en option de la classe <code>KeyStoreLoginModule</code>
 * Voir la classe <code>KeyStoreLoginModule</code> pour plus de détails
 * sur la manière de constituer le fichier de configuration de connexion JAAS.
 * <p>
 * Cette classe n'est là qu'à titre d'exemple et à des fins de tests rapides. Les utilisateurs doivent
 * écrire leur propre implémentation d'Authenticator qui correspondra mieux
 * à leur environnement.
 *
 * @ibm-api
 * @since WAS XD 6.0.1
 *
 * @see Authenticator
 * @see KeyStoreLoginModule
 * @see UserPasswordCredential
 */
public class KeyStoreLoginAuthenticator implements Authenticator {

    /**
     * Crée un nouveau KeyStoreLoginAuthenticator.
     */
    public KeyStoreLoginAuthenticator() {
        super();
    }

    /**
     * Authentifie un <code>UserPasswordCredential</code>.
     * <p>
     * Utilise le nom d'utilisateur et le mot de passe de l'UserPasswordCredential spécifié
     * pou se connecter au KeyStoreLoginModule nommé "KeyStoreLogin".
     *
     * @throws InvalidCredentialException si les données d'identification ne sont pas
     *         UserPasswordCredential ou si une erreur se produit pendant le traitement
     *         de l'UserPasswordCredential fourni
     *
     * @throws ExpiredCredentialException si les données d'identification ont expiré.  Cette exception
     *         n'est pas utilisée par cette implémentation
     *
     * @see Authenticator#authenticate(Credential)
     * @see KeyStoreLoginModule
     */
    public Subject authenticate(Credential credential) throws InvalidCredentialException, 
			ExpiredCredentialException {

        if (credential == null) {
            throw new InvalidCredentialException("Supplied credential is null");
        }

        if ( ! (credential instanceof UserPasswordCredential) ) {
            throw new InvalidCredentialException("Supplied credential is not a UserPasswordCredential");
        }

        UserPasswordCredential cred = (UserPasswordCredential) credential;
        LoginContext lc = null;
        try {
            lc = new LoginContext("KeyStoreLogin",
                    new UserPasswordCallbackHandlerImpl(cred.getUserName(), cred.getPassword().toCharArray()));

            lc.login();

            Subject subject = lc.getSubject();

            return subject;
        }
        catch (LoginException le) {
            throw new InvalidCredentialException(le);
        }
        catch (IllegalArgumentException ile) {
            throw new InvalidCredentialException(ile);
        }
    }
}
KeyStoreLoginModule.java
// Cet exemple de programme est fourni TEL QUEL et peut être utilisé, exécuté, copié et modifié
// gratuitement par le client
// (a) à des fins d'études,
// (b) afin de développer des applications conçues pour être exécutées avec un produit IBM WebSphere,
// soit pour un usage interne soit pour une redistribution par le client, en tant que partie de
// l'application, au sein des produits du client.
//   Eléments sous licence - Propriété d'IBM
// 5724-J34 © COPYRIGHT International Business Machines Corp. 2007
package com.ibm.websphere.objectgrid.security.plugins.builtins;

import java.io.File;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;

import com.ibm.websphere.objectgrid.ObjectGridRuntimeException;
import com.ibm.ws.objectgrid.Constants;
import com.ibm.ws.objectgrid.ObjectGridManagerImpl;
import com.ibm.ws.objectgrid.util.ObjectGridUtil;

/**
 * Un KeyStoreLoginModule est un module de connexion d'authentification JAAS
 * auprès d'un magasin de clés.
 * <p>
 * Une configuration de connexion doit fournir une option "<code>keyStoreFile</code>" 
 * pour indiquer où se trouve le fichier de clés. Si la valeur <code>keyStoreFile</code> 
 * contient une propriété système de la forme <code>${system.property}</code>,
 * il sera étendu à la valeur de cette propriété système.
 * <p>
 * S'il n'est pas fourni d'option "<code>keyStoreFile</code>", le nom par défaut du fichier de clés
 * est <code>"${java.home}${/}.keystore"</code>.
 * <p>
 * Voici un exemple de configuration du module de connexion : 
 * <pre><code>
 *    KeyStoreLogin {
 *        com.ibm.websphere.objectgrid.security.plugins.builtins.KeystoreLoginModule required
 *            keyStoreFile="${user.dir}${/}security${/}.keystore";
 *    };
 * </code></pre>
 * 
 * @ibm-api
 * @since WAS XD 6.0.1  
 * 
 * @see LoginModule
 */
public class KeyStoreLoginModule implements LoginModule {

    private static final String CLASS_NAME = KeyStoreLoginModule.class.getName();

    /** 
     * Nom de la propriété du fichier de clés 
     */
    public static final String KEY_STORE_FILE_PROPERTY_NAME = "keyStoreFile";

    /**
     * Type du fichiers de clés. Seul JKS est pris en charge 
     */
    public static final String KEYSTORE_TYPE = "JKS";

    /**
     * Le nom par défaut du fichier de clés 
     */
    public static final String DEFAULT_KEY_STORE_FILE = "${java.home}${/}.keystore";

    private CallbackHandler handler;

    private Subject subject;

    private boolean debug = false;

    private Set principals = new HashSet();

    private Set publicCreds = new HashSet();

    private Set privateCreds = new HashSet();

    protected KeyStore keyStore;

    /**
     * Crée un nouveau KeyStoreLoginModule.
     */
    public KeyStoreLoginModule() {
    }

    /**
     * Initialise le module de connexion.
     * 
     * @see LoginModule#initialize(Subject, CallbackHandler, Map, Map)
     */
    public void initialize(Subject sub, CallbackHandler callbackHandler,
            Map mapSharedState, Map mapOptions) {

        // initialisation de toutes les options configurées
        debug = "true".equalsIgnoreCase((String) mapOptions.get("debug"));

        if (sub == null)
            throw new IllegalArgumentException("Subject is not specified"); 

        if (callbackHandler == null)
            throw new IllegalArgumentException(
            "CallbackHander is not specified"); 

        // Obtention du chemin du magasin de clés
        String sKeyStorePath = (String) mapOptions
            .get(KEY_STORE_FILE_PROPERTY_NAME);

        // S'il n'y a pas de chemin du magasin de clés, le chemin par défaut est le fichier .keystore
        // dans le répertoire de base de java
        if (sKeyStorePath == null) {
            sKeyStorePath = DEFAULT_KEY_STORE_FILE;
        }

        // Replace the system enviroment variable
        sKeyStorePath = ObjectGridUtil.replaceVar(sKeyStorePath);

        File fileKeyStore = new File(sKeyStorePath);

        try {
            KeyStore store = KeyStore.getInstance("JKS");
            store.load(new FileInputStream(fileKeyStore), null);

            // Enregistrement du magasin de clés
            keyStore = store;

            if (debug) {
                System.out.println("[KeyStoreLoginModule] initialize: Successfully loaded key store");
            }
        }
        catch (Exception e) {
            ObjectGridRuntimeException re = new ObjectGridRuntimeException(
                    "Failed to load keystore: " + fileKeyStore.getAbsolutePath()); 
            re.initCause(e);
            if (debug) {
                System.out.println("[KeyStoreLoginModule] initialize: Key store loading failed with exception "
                        + e.getMessage());
            }
        }

        this.subject = sub;
        this.handler = callbackHandler;
    }

    /**
     * Authentification d'un utilisateur à partir du fichier de clés.
     * 
     * @see LoginModule#login()
     */
    public boolean login() throws LoginException {

        if (debug) {
            System.out.println("[KeyStoreLoginModule] login: entry");
        }

        String name = null;
        char pwd[] = null;

        if (keyStore == null || subject == null || handler == null) {
            throw new LoginException("Module initialization failed"); 
        }

        NameCallback nameCallback = new NameCallback("Username:");
        PasswordCallback pwdCallback = new PasswordCallback("Password:", false);

        try {
            handler.handle(new Callback[] { nameCallback, pwdCallback });
        }
        catch (Exception e) {
            throw new LoginException("Callback failed: " + e); 
        }

        name = nameCallback.getName();
        char[] tempPwd = pwdCallback.getPassword();

        if (tempPwd == null) {
            // treat a NULL password as an empty password
            tempPwd = new char[0];
        }
        pwd = new char[tempPwd.length];
        System.arraycopy(tempPwd, 0, pwd, 0, tempPwd.length);

        pwdCallback.clearPassword();

        if (debug) {
            System.out.println("[KeyStoreLoginModule] login: "
                    + "user entered user name: " + name);
        }

        // Validation du nom d'utilisateur et du mot de passe
        try {
            validate(name, pwd);
        }
        catch (SecurityException se) {
            principals.clear();
            publicCreds.clear();
            privateCreds.clear();
            LoginException le = new LoginException(
            "Exception encountered during login");
            le.initCause(se);

            throw le;
        }

        if (debug) {
            System.out.println("[KeyStoreLoginModule] login: exit");
        }
        return true;
    }

    /**
     * Indique si l'utilisateur est accepté. 
     * <p>
     * Cette méthode n'est appelée que si l'utilisateur est authentifié par tous les modules
     * du fichier de configuration de connexion. Les objets principaux seront ajoutés 
     * au subject stocké.
     * 
     * @return false si pour une raison ou pour une autre les principaux ne peuvent être ajoutés; true
     *         autrement
     * 
     * @exception LoginException
     *                Une LoginException est levée si le est en lecture seule
     *                ou si des exceptions irrécupérables sont rencontrées.
     * 
     * @see LoginModule#commit()
     */
    public boolean commit() throws LoginException {
        if (debug) {
            System.out.println("[KeyStoreLoginModule] commit: entry");
        }

        if (principals.isEmpty()) {
            throw new IllegalStateException("Commit is called out of sequence");
        }

        if (subject.isReadOnly()) {
            throw new LoginException("Subject is Readonly");
        }

        subject.getPrincipals().addAll(principals);
        subject.getPublicCredentials().addAll(publicCreds);
        subject.getPrivateCredentials().addAll(privateCreds);

        principals.clear();
        publicCreds.clear();
        privateCreds.clear();

        if (debug) {
            System.out.println("[KeyStoreLoginModule] commit: exit");
        }
        return true;
    }

    /**
     * Indique que l'utilisateur n'est pas accepté
     * 
     * @see LoginModule#abort()
     */
    public boolean abort() throws LoginException {
        boolean b = logout();
        return b;
    }

    /**
     * Déconnecte l'utilisateur. Efface toutes les mappes.
     * 
     * @see LoginModule#logout()
     */
    public boolean logout() throws LoginException {


        // Effacement des variables d'instance
        principals.clear();
        publicCreds.clear();
        privateCreds.clear();

        // Efacement des mappes dans le subject
        if (!subject.isReadOnly()) {
            if (subject.getPrincipals() != null) {
                subject.getPrincipals().clear();
            }

            if (subject.getPublicCredentials() != null) {
                subject.getPublicCredentials().clear();
            }

            if (subject.getPrivateCredentials() != null) {
                subject.getPrivateCredentials().clear();
            }
        }
        return true;
    }

    /**
     * Validation du nom d'utilisateur et du mot de passe d'après le magasin de clés.
     * 
     * @param userName user name
     * @param password password
     * @throws SecurityException if any exceptions encountered
     */
    private void validate(String userName, char password[])
        throws SecurityException {

        PrivateKey privateKey = null;

        // Obtention de la clé privée depuis le magasin de clés
        try {
            privateKey = (PrivateKey) keyStore.getKey(userName, password);
        }
        catch (NoSuchAlgorithmException nsae) {
            SecurityException se = new SecurityException();
            se.initCause(nsae);
            throw se;
        }
        catch (KeyStoreException kse) {
            SecurityException se = new SecurityException();
            se.initCause(kse);
            throw se;
        }
        catch (UnrecoverableKeyException uke) {
            SecurityException se = new SecurityException();
            se.initCause(uke);
            throw se;
        }

        if (privateKey == null) {
            throw new SecurityException("Invalid name: " + userName); 
        }

        // Vérification des certificats
        Certificate certs[] = null;
        try {
            certs = keyStore.getCertificateChain(userName);
        }
        catch (KeyStoreException kse) {
            SecurityException se = new SecurityException();
            se.initCause(kse);
            throw se;
        }

        if (debug) {
            System.out.println("  Print out the certificates:");
            for (int i = 0; i < certs.length; i++) {
                System.out.println("  certificate " + i);
                System.out.println("    " + certs[i]);
            }
        }

        if (certs != null && certs.length > 0) {

            // Si le premier certificat est un X509Certificate
            if (certs[0] instanceof X509Certificate) {
                try {
                    // Obtention du premier certificat qui représente l'utilisateur
                    X509Certificate certX509 = (X509Certificate) certs[0];

                    // Création d'un principal
                    X500Principal principal = new X500Principal(certX509
                            .getIssuerDN()
                            .getName());
                    principals.add(principal);

                    if (debug) {
                        System.out.println("  Principal added: " + principal);
                    }
                    // Création de l'objet de chemin de certification et ajout de cet objet 
                    // à l'ensemble des données d'identification publiques
                    CertificateFactory factory = CertificateFactory
                        .getInstance("X.509");
                    java.security.cert.CertPath certPath = factory
                        .generateCertPath(Arrays.asList(certs));
                    publicCreds.add(certPath);

                    // Ajout des données d'identification privées au jeu de données d'identification privées
                    privateCreds.add(new X500PrivateCredential(certX509,
                            privateKey, userName));

                }
                catch (CertificateException ce) {
                    SecurityException se = new SecurityException();
                    se.initCause(ce);
                    throw se;
                }
            }
            else {
                // Le premier certificat n'est pas un X509Certificate
                // Nous n'ajoutons le certificat qu'au jeu des données d'identification publiques
                // et la clé privée au jeu de données d'identification privées.
                publicCreds.add(certs[0]);
                privateCreds.add(privateKey);
            }
        }
    }
}

Utiliser le plug-in d'authentificateur pour LDAP

L'implémentation de com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPAuthenticator est fournie par défaut pour permettre de gérer l'authentification par nom d'utilisateur et mot de passe auprès d'un serveur LDAP. Cette implémentation utilise le module de connexion LDAPLogin pour permettre à l'utilisateur d'ouvrir une session sur un serveur LDAP. Le fragment de code suivant montre comment est implémentée la méthode authenticate :

/**
* @see com.ibm.ws.objectgrid.security.plugins.Authenticator#
* authenticate(LDAPLogin)
*/
public Subject authenticate(Credential credential) throws
InvalidCredentialException, ExpiredCredentialException {

    UserPasswordCredential cred = (UserPasswordCredential) credential;
    LoginContext lc = null;
    try {
        lc = new LoginContext("LDAPLogin",
            new UserPasswordCallbackHandlerImpl(cred.getUserName(),
            cred.getPassword().toCharArray()));

        lc.login();

        Subject subject = lc.getSubject();

        return subject;
    }
    catch (LoginException le) {
        throw new InvalidCredentialException(le);
    }
    catch (IllegalArgumentException ile) {
        throw new InvalidCredentialException(ile);
    }
}

Par ailleurs, eXtreme Scale est livré à cette fin avec le module de connexion com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPLoginModule. Vous devez fournir les deux options suivantes dans le fichier de configuration des connexions JAAS.

Le module LDAPLoginModule appelle la méthode com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPAuthentcationHelper.authenticate. Le fragment de code suivant indique comment implémenter la méthode authenticate de LDAPAuthenticationHelper.

/**
* Authenticate the user to the LDAP directory.
* @param user the user ID, e.g., uid=xxxxxx,c=us,ou=bluepages,o=ibm.com
* @param pwd the password
*
* @throws NamingException
*/
public String[] authenticate(String user, String pwd)
throws NamingException {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY, factoryClass);
    env.put(Context.PROVIDER_URL, providerURL);
    env.put(Context.SECURITY_PRINCIPAL, user);
    env.put(Context.SECURITY_CREDENTIALS, pwd);
    env.put(Context.SECURITY_AUTHENTICATION, "simple");

    InitialContext initialContext = new InitialContext(env);

    // Look up for the user
    DirContext dirCtx = (DirContext) initialContext.lookup(user);

    String uid = null;
    int iComma = user.indexOf(",");
    int iEqual = user.indexOf("=");
    if (iComma > 0 && iComma > 0) {
        uid = user.substring(iEqual + 1, iComma);
    }
    else {
        uid = user;
    }

    Attributes attributes = dirCtx.getAttributes("");

    // Check the UID
    String thisUID = (String) (attributes.get(UID).get());

    String thisDept = (String) (attributes.get(HR_DEPT).get());

    if (thisUID.equals(uid)) {
        return new String[] { thisUID, thisDept };
    }
    else {
        return null;
    }
}

Si l'authentification réussit, l'ID et le mot de passe sont considérés comme valides. Puis le module de connexion obtient de cette méthode authenticate l'ID et le département. Le module de connexion crée deux principaux : SimpleUserPrincipal et SimpleDeptPrincipal. Vous pouvez utiliser le Subject authentifié pour l'autorisation de groupe (dans notre cas, le département est un groupe) et pour l'autorisation individuelle.

L'exemple qui suit montre une configuration de module de connexion utilisée pour ouvrir une session sur le serveur LDAP :

LDAPLogin { com.ibm.websphere.objectgrid.security.plugins.builtins.LDAPLoginModule required
    providerURL="ldap://directory.acme.com:389/"
    factoryClass="com.sun.jndi.ldap.LdapCtxFactory";
};

Dans cette configuration, le serveur LDAP pointe sur ldap://directory.acme.com:389/server. Vous utiliserez ici l'URL de votre serveur LDAP. Ce module de connexion utilise l'ID et le mot de passe fournis pour se connecter au serveur LDAP. Cette implémentation n'est là qu'à des fins de test.

Utiliser le plug-in d'authentificateur pour WebSphere Application Server

eXtreme Scale intègre également l'implémentation de com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenAuthenticator qui permet d'utiliser l'infrastructure de sécurité de WebSphere Application Server. Cette implémentation pré-intégrée est utilisable lorsque les conditions suivantes sont réunies.

  1. La sécurité globale de WebSphere Application Server est activée.
  2. Tous les clients et les serveurs eXtreme Scale sont lancés sur des machines virtuelles Java WebSphere Application Server.
  3. Ces serveurs d'applications se trouvent tous dans le même domaine de sécurité.
  4. Le client eXtreme Scale est déjà authentifié dans WebSphere Application Server.

Le client peut utiliser la classe com.ibm.websphere.objectgrid.security.plugins.builtins.WSTokenCredentialGenerator pour générer des données d'identification. Le serveur utilise cette classe d'implémentation Authenticator pour authentifier ces données. Si la clé est authentifiée, un objet Subject est renvoyé.

Ce scénario exploite le fait que le client a déjà été authentifié. Du fait que les serveurs d'applications sur lesquels sont les serveurs se trouvent dans le même domaine de sécurité que ceux qui hébergent les clients, les jetons de sécurité peuvent être propagés du client vers le serveur de sorte que le même registre d'utilisateurs n'a pas besoin d'être authentifié à nouveau.

Utiliser le plug-in d'authentificateur Tivoli Access Manager plug-in

Tivoli Access Manager connaît une large utilisation comme serveur de sécurité. Vous pouvez également implémenter Authenticator à l'aide des modules de connexion fournis par Tivoli Access Manager.

Pour authentifier un utilisateur pour Tivoli Access Manager, appliquez le module de connexion com.tivoli.mts.PDLoginModule, lequel requiert que l'application appelante fournisse les informations suivantes :

  1. un nom de principal, spécifié soit sous la forme d'un nom court, soit d'un nom X.500 (DN)
  2. un mot de passe

Le module de connexion authentifie le principal et retourne les données d'identification Tivoli Access Manager. Le module de connexion attend de l'application appelante les informations suivantes :

  1. le nom d'utilisateur, via un objet javax.security.auth.callback.NameCallback
  2. le mot de passe, via un objet javax.security.auth.callback.PasswordCallback

Lorsque les données d'identification Tivoli Access Manager ont pu être récupérées, le JAAS LoginModule crée un Subject et un PDPrincipal. Aucune intégration de l'authentification pour Tivoli Access Manager n'est fournie car celle-ci l'est avec le module PDLoginModule. Pour plus de détails, voir le manuel IBM® Tivoli Access Manager Authorization Java Classes Developer Reference.

Se connecter de manière sécurisée à WebSphere eXtreme Scale

Pour connecter de manière sécurisée un client eXtreme Scale à un serveur, vous pouvez utiliser n'importe quelle méthode connect de l'interface ObjectGridManager qui accepte un objet ClientSecurityConfiguration. Voici un exemple succinct.

public ClientClusterContext connect(String catalogServerEndpoints, 
		 ClientSecurityConfiguration securityProps,
     URL overRideObjectGridXml) throws ConnectException;

Cette méthode reçoit un paramètre du type ClientSecurityConfiguration, qui est une interface représentant une configuration de la sécurité d'un client. Vous pouvez utiliser l'API publique com.ibm.websphere.objectgrid.security.config.ClientSecurityConfigurationFactory pour créer une instance avec des valeurs par défaut. Vous pouvez également créer une instance en passant le fichier de propriétés du client WebSphere eXtreme Scale. Ce fichier contient les propriétés suivantes qui intéressent l'authentification. La valeur marquée par un signe plus (+) est la valeur par défaut.

  • securityEnabled (true, false+): Cette propriété indique si la sécurité est activée. Lorsqu'un client se connecte à un serveur, la valeur securityEnable côté client et côté serveur doit être identique des deux côtés : égale à true ou à false dans les deux cas. Par exemple, si la sécurité du serveur connecté est activée, la valeur de la propriété doit être associée à true du côté client pour que le client puisse se connecter au serveur.
  • authenticationRetryCount (an integer value, 0+) : Cette propriété détermine le nombre de tentatives de connexion à effectuer lorsque des données d'identification ont expiré. Une valeur de 0 indique qu'aucune nouvelle tentative n'est effectuée. La tentative d'authentification ne s'applique qu'au cas où les données d'identification sont arrivées à expiration. Si les données ne sont pas valides, aucune nouvelle tentative n'est effectuée. C'est à votre application de relancer les tentatives.

Après avoir créé un objet com.ibm.websphere.objectgrid.security.config.ClientSecurityConfiguration, définissez l'objet credentialGenerator sur le client en utilisant la méthode suivante :

/**
* Set the {@link CredentialGenerator} object for this client.
* @param generator the CredentialGenerator object associated with this client
*/
void setCredentialGenerator(CredentialGenerator generator);
Vous pouvez définir l'objet CredentialGenerator également dans le fichier de propriétés du client WebSphere eXtreme Scale :
  • credentialGeneratorClass : Le nom de l'implémentation de classe de l'objet CredentialGenerator. Cette classe doit avoir un constructeur par défaut.
  • credentialGeneratorProps : Les propriétés de la classe CredentialGenerator. Si la valeur n'est pas null, elle est définie à l'aide de la méthode setProperties(String) comme l'objet CredentialGenerator qui est construit.

Voici un exemple d'instanciation de ClientSecurityConfiguration, utilisée ensuite pour se connecter au serveur.

/**
* Get a secure ClientClusterContext
* @return a secure ClientClusterContext object
*/
protected ClientClusterContext connect() throws ConnectException {
ClientSecurityConfiguration csConfig = ClientSecurityConfigurationFactory
.getClientSecurityConfiguration("/properties/security.ogclient.props");

UserPasswordCredentialGenerator gen= new
UserPasswordCredentialGenerator("manager", "manager1");

csConfig.setCredentialGenerator(gen);

return objectGridManager.connect(csConfig, null);
}

Lorsque la méthode connect est appelée, le client WebSphere eXtreme Scale appelle la méthode CredentialGenerator.getCredential pour obtenir les données d'identification du client. Ces données sont envoyées pour authentification au serveur avec la demande de connexion.

Utiliser une instance CredentialGenerator différente par session

Dans certains cas, un client WebSphere eXtreme Scale ne représente qu'une seule identité de client, alors que, dans d'autres, il représentera des identités multiples. Voici un scénario pour ce dernier cas : un client WebSphere eXtreme Scale est créé et partagé sur un serveur Web. Tous les servlets de ce serveur Web utilisent cet unique client WebSphere eXtreme Scale. Chaque servlet représentant un client Web différent, vous devez utiliser des données d'identification différentes lorsque vous envoyez des demandes aux serveurs WebSphere eXtreme Scale.

WebSphere eXtreme Scale permet le changement des données d'identification au niveau session. Chaque session peut en effet utiliser un objet CredentialGenerator différent. En conséquence de quoi, les scénarios précédents peuvent être implémentés en laissant le servlet obtenir une session avec un objet CredentialGenerator différent. L'exemple qui suit illustre la méthode ObjectGrid.getSession(CredentialGenerator) dans l'interface ObjectGridManager.

/**
     * Obtention d'une session à l'aide de <code>CredentialGenerator</code>.
     * <p>
     * Cette méthode ne peut être appelée que par le client ObjectGrid
     * dans environnement client/serveur ObjectGrid. Si ObjectGrid est utilisé dans un modèle local, c'est-à-dire
     * au sein de la même JVM sans aucun client ou serveur existant, <code>getSession(Subject)</code>
     * ou le plug-in <code>SubjectSource</code> doivent être utilisés pour sécuriser l'ObjectGrid.
     *
     * <p>Si la méthode <code>initialize()</code> n'a pas été appelée avant
     * le premier appel à <code>getSession</code>, une initialisation implicite
     * va se produire.  C'est la garantie que toute la configuration est effectuée
     * avant que toute utilisation d'exécution ne soit requise.</p>
     *
     * @param credGen A <code>CredentialGenerator</code> pour générer des données d'identification
     *                pour la session retournée.
     *
     * @return Une instance de <code>Session</code>
     *
     * @throws ObjectGridException si une erreur se produit durant le traitement
     * @throws TransactionCallbackException si le <code>TransactionCallback</code>
     *         lève une exception
     * @throws IllegalStateException si cette méthode est appelée après
     *         l'appel à la méthode <code>destroy()</code>.
     *
     * @see #destroy()
     * @see #initialize()
     * @see CredentialGenerator
     * @see Session
     * @since WAS XD 6.0.1
 */
Session getSession(CredentialGenerator credGen) throws
ObjectGridException, TransactionCallbackException;

Voici un exemple :

ObjectGridManager ogManager = ObjectGridManagerFactory.getObjectGridManager();

CredentialGenerator credGenManager = new UserPasswordCredentialGenerator("manager", "xxxxxx");
CredentialGenerator credGenEmployee = new UserPasswordCredentialGenerator("employee", "xxxxxx");

ObjectGrid og = ogManager.getObjectGrid(ctx, "accounting");

// L'on obtient une session avec CredentialGenerator;
Session session = og.getSession(credGenManager );

// Obtenir la mappe d'employé
ObjectMap om = session.getMap("employee");

// Démarrer une transaction.
session.begin();

Object rec1 = map.get("xxxxxx");

session.commit();

// L'on obtient une autre Gsession avec un autre CredentialGenerator;
session = og.getSession(credGenEmployee );

// Obtenir la mappe d'employé
om = session.getMap("employee");

// Démarrer une transaction.
session.begin();

Object rec2 = map.get("xxxxx");

session.commit();

Si vous utilisez la méthode ObjectGrid.getSession pour obtenir un objet Session, la session utilisera l'objet CredentialGenerator défini dans l'objet ClientConfigurationSecurity. La méthode ObjectGrid.getSession(CredentialGenerator) remplace le CredentialGenerator défini dans l'objet ClientSecurityConfiguration.

Et les performances ne pourront qu'y gagner si vous pouvez réutiliser l'objet Session. Cela dit, l'appel à la méthode ObjectGrid.getSession(CredentialGenerator) ne coûte guère. Le temps système est essentiellement dû à l'augmentation du temps passé à récupérer de la place. Veillez à bien libérer les références une fois que vous en avez fini avec les objets Session. En général, si votre objet Session peut partager l'identité, essayez de le réutiliser. Sinon, utilisez la méthode ObjectGrid.getSession(CredentialGenerator).