IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

 

Développons en Java   2.30  
Copyright (C) 1999-2022 Jean-Michel DOUDOUX    (date de publication : 15/06/2022)

[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

 

29. JNDI (Java Naming and Directory Interface)

 

chapitre    2 9

 

Niveau : niveau 4 Supérieur 

 

JNDI est l'acronyme de Java Naming and Directory Interface. Cette API fournit une interface unique pour utiliser différents services de nommages ou d'annuaires et définit une API standard pour permettre l'accès à ces services.

Il existe plusieurs types de services de nommage parmi lesquels :

  • DNS (Domain Name System) : service de nommage utilisé sur internet pour permettre la correspondance entre un nom de domaine et une adresse IP
  • LDAP(Lightweight Directory Access Protocol) : annuaire
  • NIS (Network Information System) : service de nommage réseau développé par Sun Microsystems
  • COS Naming (Common Object Services) : service de nommage utilisé par Corba pour stocker et obtenir des références sur des objets Corba
  • etc, ...

Un service de nommage permet d'associer un nom unique à un objet et de faciliter ainsi l'obtention de cet objet.

Un annuaire est un service de nommage qui possède en plus une représentation hiérarchique des objets qu'il contient et un mécanisme de recherche.

JNDI propose donc une abstraction pour permettre l'accès à ces différents services de manière standard. Ceci est possible grâce à l'implémentation de pilotes qui mettent en oeuvre la partie SPI (Service Provider Interface) de l'API JNDI. Cette implémentation se charge d'assurer le dialogue entre l'API et le service utilisé.

JNDI possède un rôle particulier dans les architectures applicatives développées en Java car elle est utilisée dans les spécifications de plusieurs API majeures : JDBC, EJB, JMS, ...

De plus, la centralisation de données dans une source unique pour une ou plusieurs applications facilite l'administration de ces données et leur accès.

Oracle propose un tutorial sur JNDI à l'url : https://docs.oracle.com/javase/jndi/tutorial/ .

Pour utiliser JNDI, il faut un service de nommage correctement installé et configuré et un pilote dédié à ce service.

Ce chapitre contient plusieurs sections :

 

29.1. La présentation de JNDI

JNDI est composée de deux parties

  • Une API utilisée pour le développement des applications
  • Une SPI utilisée par les fournisseurs d'une implémentation d'un pilote

Un pilote est un ensemble de classes qui implémentent les interfaces de JNDI pour permettre les interactions avec un service particulier. Ce mode de fonctionnement est identique à celui proposé par l'API JDBC.

Il est donc nécessaire de disposer d'un pilote pour assurer le dialogue entre l'application via l'API et le service de nommage ou l'annuaire. La partie API est incluse dans le JDK et Sun propose une implémentation des pilotes pour LDAP, DNS et Corba. Pour d'autres services ou implémentations, il faut utiliser des implémentations des pilotes fournis par des fournisseurs tiers.

  

Pour définir une connexion, JNDI à besoin d'au moins deux éléments :

  • La fabrique du contexte racine : c'est cet objet qui assure le dialogue avec le service en utilisant le protocole adéquat
  • L'url du service à utiliser

JNDI n'est pas utilisable uniquement pour des applications J2EE. Une application standalone peut par exemple réaliser une authentification à partir d'un annuaire grâce au protocole LDAP.

Ainsi JNDI est inclus dans J2SE depuis la version 1.3. Pour les versions antérieures (J2SE 1.1 et 1.2), il est nécessaire de télécharger JNDI en tant qu'extension standard et de l'installer.

Pilote
J2SE 1.3
J2SE 1.4
LDAP
Oui
Oui
Corba COS
Oui
Oui
Registre RMI
Oui
Oui
DNS
Non
Oui

Il est aussi possible d'utiliser d'autres pilotes fournis séparément par Sun ou par d'autres fournisseurs.

 

29.1.1. Les services de nommage

Il existe de nombreux services de nommage : les plus connus sont sûrement les systèmes de fichiers (File system), les DNS, les annuaires LDAP, ...

Un service de nommage permet d'associer un nom à un objet ou à une référence sur un objet. L'objet associé dépend du service : un fichier dans un système de fichiers, une adresse I.P. dans un DNS, ...

Le nom associé à un objet respecte une convention de nommage particulière à chaque type de service.

  • Avec un système de fichiers de type Unix, le nom est composé d'éléments séparés par des caractères "/"
  • Avec un système de fichiers de type Windows, le nom est composé d'éléments séparés par des caractères "\"
  • Avec un service de type DNS, le nom est composé d'éléments séparés par des caractères "." (exemple : www.test.fr).
  • Avec un service de type LDAP, le nom désigné par le terme Distinguished Name est composé d'éléments séparés par des caractères ",". Un élément est de la forme clé=valeur.

Pour permettre une abstraction des différents formats de noms utilisés par les différents services, JNDI utilise la classe Name.

 

29.1.2. Les annuaires

Un annuaire est un outil qui permet de stocker et de consulter des informations selon un protocole particulier. Un annuaire est plus particulièrement dédié à la recherche et la lecture d'informations : il est optimisé pour ce type d'activité mais il doit aussi être capable d'ajouter et de modifier des informations.

Les annuaires sont des extensions des services de nommage en ajoutant en plus la possibilité d'associer d'éventuels attributs à chaque objet.

Caractéristiques
Annuaire
Bases de données
Accès aux données
Lecture privilégiée
Lecture et modification
Représentation des données
Hiérarchique
Ensembliste

Les annuaires les plus connus dans le monde réel sont les pages jaunes et les pages blanches du principal opérateur téléphonique. Même si le but de ces deux annuaires est identique (obtenir un numéro de téléphone), la structure des données est différentes :

  • Pages blanches : regroupement par département, ville, nom/prenom
  • Pages jaunes : regroupement par activités, ville, nom

Les systèmes de fichiers sont aussi des annuaires : ils associent un nom à un fichier mais stockent aussi des attributs liés à ces fichiers (droits d'accès, dates de création et de modification, ...)

 

29.1.3. Le contexte

Un service de nommage permet d'associer un nom à un objet. Cette association est nommée binding. Un ensemble d'associations nom/objet est nommé un contexte.

Ce contexte est utilisé lors de l'accès à un élément contenu dans le service.

Il existe deux types de contexte :

  • Contexte racine
  • Sous contexte

Un sous-contexte est un contexte relatif à un contexte racine.

Par exemple, c:\ est un contexte racine dans un système de fichiers de type Windows. Le répertoire windows (C:\windows) est un sous-contexte du contexte racine qui est dans ce cas nommé sous-répertoire.

Dans DNS, com est un contexte racine et test est un sous contexte (test.com)

 

29.2. La mise en oeuvre de l'API JNDI

L'API JNDI est contenue dans cinq packages :

Packages Rôle
javax.naming Classes et interfaces pour utiliser un service de nommage
javax.naming.directory Etend les fonctionnalités du package javax.naming pour l'utilisation des services de type annuaire
javax.naming.event Classes et interfaces pour la gestion des événements lors d'un accès à un service
javax.naming.ldap Etend les fonctionnalités du package javax.naming.directory pour l'utilisation de la version 3 de LDAP
javax.naming.spi Classes et interfaces dédiées aux Service Provider pour le développement de pilotes

 

29.2.1. L'interface Name

Cette interface encapsule un nom en permettant de faire abstraction des conventions de nommage utilisées par le service.

Deux classes implémentent cette interface :

  • CompositeName : chaque élément qui compose le CompositeName est séparé par un caractère /
  • CompoundName : chaque élément issu de la hiérarchie compose le nom selon certaines règles dépendantes de l'implémentation

 

29.2.2. L'interface Context et la classe IntitialContext

L'interface javax.Naming.Context représente un ensemble de correspondances nom/objet d'un service de nommage. Elle propose des méthodes pour interroger et mettre à jour ces correspondances.

Méthode Rôle
void bind(String, Object) Ajouter une nouvelle correspondance entre le nom et l'objet passé en paramètre
void rebind(String, Object) Redéfinir l'association nom - objet en écrasant la précédente correspondance si elle existe
Object lookup(String) Renvoyer un objet à partir de son nom
void unbind(String) Supprimer la correspondance désignée par le nom fourni en paramètre
void rename(String, String) Modifier le nom d'une correspondance
NamingEnumeration listBindings(String) Obtenir une énumération des noms et de leurs objets associés pour le contexte passé en paramètre
NamingEnumeration list(String) Obtenir une énumération des noms et des classes des objets associés pour le contexte passé en paramètre

Toutes ces méthodes possèdent une version surchargée qui attend le nom de la correspondance sous la forme d'un objet de type Name.

La classe javax.Naming.InitialContext qui implémente l'interface Context encapsule le contexte racine : c'est le noeud qui sert de point d'entrée lors de la connexion avec le service.

Toutes les opérations réalisées avec JNDI sont relatives à ce contexte racine.

Pour obtenir une instance de la classe InitialContext et ainsi réaliser la connexion au service, plusieurs paramètres sont nécessaires :

  • java.naming.factory.initial permet de préciser le nom de la fabrique proposée par le fournisseur. Cette fabrique est en charge de l'instanciation d'un objet de type InitialContext
  • java.naming.provider.url : URL du context racine

Plusieurs fabriques sont fournies en standard dans J2SE 1.4 :

Service Fabrique
CORBA com.sun.jndi.cosnaming.CNCtxFactory
DNS com.sun.jndi.dns.DnsContextFactory
LDAP com.sun.jndi.ldap.LdapCtxFactory
RMI com.sun.jndi.rmi.registry.RegistryContextFactory

Ces deux paramètres sont obligatoires mais d'autres peuvent être nécessaires notamment ceux concernant la sécurité pour l'accès au service.

L'interface Context définit des constantes pour le nom de ces paramètres. Il y a plusieurs moyens pour les définir :

  • les définir sous la forme de variables d'environnement passées à la JVM en utilisant l'option -D
  • les définir sous la forme d'une collection de type Hashtable passée en paramètre au constructeur de la classe InitialContext
  • les définir dans un fichier nommé jndi.properties accessible dans le classpath
Exemple :
      Hashtable hashtableEnvironment = new Hashtable(); 
      hashtableEnvironment.put(Context.INITIAL_CONTEXT_FACTORY, 
         "com.sun.jndi.fscontext.RefFSContextFactory"); 
      hashtableEnvironment.put(Context.PROVIDER_URL, "file:c:/"); 
      Context context = new InitialContext(hashtableEnvironment); 

Il est possible de réaliser des opérations particulières à partir du Context. Attention toutefois, toutes ces opérations ne sont pas utilisables avec tous les pilotes. Par exemple, l'accès à un service de type DNS n'est possible qu'en consultation.

 

29.3. L'utilisation d'un service de nommage

Pour pouvoir utiliser un service de nommage, il faut tout d'abord obtenir un contexte racine qui va encapsuler la connexion au service.

A partir de ce contexte, il est possible de réaliser plusieurs opérations :

  • bind : associer un objet avec un nom
  • rebind : modifier une association
  • unbind : supprimer une association
  • lookup : obtenir un objet à partir de son nom
  • list : obtenir une liste des associations

Toutes les opérations possèdent deux versions surchargées attendant respectivement :

  • Un objet de type Name : cet objet encapsule une séquence ordonnée de un ou plusieurs éléments (l'intérêt de cette classe est de permettre la manipulation individuelle de chaque élément).
  • Une chaîne de caractères qui contient une séquence d'éléments

 

29.3.1. L'obtention d'un objet

Pour obtenir un objet du service de nommage, utiliser la méthode lookup() du contexte.

Exemple :
import javax.naming.*;  
...  
  public String getValeur() throws NamingException {
    Context context = new InitialContext();
    return (String) context.lookup("/config/monApplication");
  }    

Ceci peut permettre de facilement stocker des options de configuration d'une application, plutôt que de les stocker dans un fichier de configuration. C'est encore plus intéressant si le service qui stocke ces données est accessible par le réseau car cela permet de centraliser ces options de configuration.

Il peut permettre aussi de stocker des données "sensibles" comme des noms d'utilisateurs et des mots de passe pour accéder à une ressource et ainsi empêcher leur accès en clair dans un fichier de configuration.

 

29.3.2. Le stockage d'un objet

Généralement les objets à stocker doivent être d'un type particulier, dépendant du pilote utilisé : il est fréquent que de tels objets doivent implémenter une interface (java.io.Serializable, java.rmi.Remote, etc ...)

La méthode bind() permet d'associer un objet à un nom.

Exemple :
import javax.naming.*; 
... 
  public void createName() throws NamingException { 
    Context context = new InitialContext(); 
    context.bind("/config/monApplication", "valeur"); 
  }

 

29.4. L'utilisation avec un DNS

A partir de J2SE 1.4, Sun propose en standard une implémentation permettant d'accéder à un DNS par JNDI.

Exemple :
import javax.naming.*;
import javax.naming.directory.*;
import java.util.*;

public class TestDNS2 {

     public static void main(String[] args) {
        try {
           Hashtable env = new Hashtable();
           env.put("java.naming.factory.initial",
                 "com.sun.jndi.dns.DnsContextFactory");
           env.put("java.naming.provider.url", "dns://80.10.246.2/");

           DirContext ctx = new InitialDirContext(env);
           Attributes attrs = ctx.getAttributes("java.sun.com",
                 new String[] { "A" });

           for (NamingEnumeration ae = attrs.getAll(); ae.hasMoreElements();) {
              Attribute attr = (Attribute) ae.next();
              String attrId = attr.getID();
              for (Enumeration vals = attr.getAll(); 
			     vals.hasMoreElements();
			     System.out.println(attrId + ": " + vals.nextElement())
			  );
           }
           ctx.close();
        } catch (Exception e) {
           System.err.println("Probleme lors de l'interrogation du DNS: " + e);
           e.printStackTrace();
        }
     }
}

Pour permettre une exécution correcte de ce programme, il est nécessaire de mettre l'adresse IP du serveur DNS utilisé.

Lors de l'exécution, il faut fournir en paramètre le nom d'un domaine et d'un serveur.

 

29.5. L'utilisation du File System Context Provider

C'est une implémentation de référence proposée par Sun qui permet un accès à un système de fichiers par JNDI.

Cela peut paraître étonnant mais un système de fichiers peut être vu comme un service de nommage qui associe un nom (par exemple c:\temp\test.txt) à un fichier ou un répertoire

Cette implémentation n'est pas fournie en standard avec le JDK mais elle peut être téléchargée (fscontext-x.x.x.jar)

La version utilisée dans cette section est la 1_2 beta3. Il suffit de décompresser le fichier fscontext-1_2-beta3.zip dans un répertoire du système et d'ajouter les fichiers fscontext.jar et providerutil.jar du sous-répertoire lib décompressé dans le classpath de l'application.

Exemple : obtenir la liste de tous les fichiers et répertoires à la racine du disque C:
import java.util.Hashtable; 
import javax.naming.Binding; 
import javax.naming.Context; 
import javax.naming.InitialContext; 
import javax.naming.NamingEnumeration; 
import javax.naming.NamingException; 

public class TestJNDI { 

  public static void main(String[] args) { 

    try { 
      Hashtable hashtableEnvironment = new Hashtable(); 
      hashtableEnvironment.put(Context.INITIAL_CONTEXT_FACTORY,
	    "com.sun.jndi.fscontext.RefFSContextFactory"); 
      hashtableEnvironment.put(Context.PROVIDER_URL, "file:c:/"); 

      Context context = new InitialContext(hashtableEnvironment); 
      NamingEnumeration namingEnumeration = context.listBindings(""); 

      while (namingEnumeration.hasMore()) { 
        Binding binding = (Binding) namingEnumeration.next(); 
        System.out.println(binding.getName()); 
      } 

      context.close(); 
    } catch (NamingException namingexception) { 
      namingexception.printStackTrace(); 
    } 
  } 
}

Il est aussi possible de rechercher un fichier dans un répertoire. Dans ce cas, le contexte initial précisé est le répertoire dans lequel le fichier doit être recherché. La méthode lookup() recherche uniquement dans ce répertoire

Exemple :
import java.io.File;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

public class TestJNDI2 {

  public static void main(String argv[]) {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
	  "com.sun.jndi.fscontext.RefFSContextFactory");
    env.put(Context.PROVIDER_URL, "file:c:/");

    try {
      Context ctx = new InitialContext(env);
      File fichier = (File) ctx.lookup("boot.ini");
      System.out.println("objet trouve = " + fichier);
    } catch (NamingException e) {
      e.printStackTrace();
    }
  }
}

Attention, le cast effectué sur l'objet retourné par la méthode lookup() doit être pertinent en fonction du contexte.

 

29.6. LDAP

LDAP, acronyme de Lightweight Directory Access Protocol, est un protocole de communication vers un annuaire en utilisant TCP/IP. Il est une simplification du protocole X 500 (d'où le L de Lightweight).

Le but principal est de retrouver des données insérées dans l'annuaire. Ce protocole est donc optimisé pour la lecture et la recherche d'informations.

LDAP est un protocole largement supporté par l'industrie informatique : il existe de nombreuses implémentations libres et commerciales : Microsoft Active Directory, OpenLDAP, Netscape Directory Server, Sun NIS, Novell NDS, ..

Ce protocole ne précise pas comment ces données sont stockées sur le serveur. Ainsi un serveur de type LDAP peut stocker n'importe quel type de données : ce sont souvent des ressources (personnes, matériels réseaux, ...).

La version actuelle de LDAP est la v3 définie par les RFC 2252 et RFR 2256 de l'IETF.

Dans un annuaire LDAP, les noeuds sont organisés sous une forme arborescente hiérarchique nommée le DIT (Direct Information Tree). Chaque noeud de cette arborescence représente une entrée dans l'annuaire. Chaque entrée contient un objet qui possède un ou plusieurs attributs dont les valeurs permettent d'obtenir des informations sur l'objet. Un objet appartient à une classe au sens LDAP.

La première entrée dans l'arborescence est nommée racine et est unique.

Chaque objet possède un Relative Distinguish Name (RDN) qui correspond à une paire clé/valeur d'un attribut obligatoire. Un objet est identifié de façon unique grâce à sa référence unique dans le DIT : son Distinguish Name (DN) qui est composé de l'ensemble des RDN de chaque objet père dans l'arborescence lue de droite à gauche et son RDN (ceci correspond donc au DN de l'entrée père et de son RDN). Cette référence représente donc le chemin d'accès depuis la racine de l'arborescence. Le DN se lit de droite à gauche puisque la racine est à droite.

La convention de nommage utilisée pour le DN, utilise la virgule comme séparateur et se lit de droite à gauche.

Exemple :
uid=jm,ou=utilisateur,o=test.com

Le premier élément du DN, nommé Relative Distinguished Name (RDN), est composé d'une paire clé/valeur. Comme valeur de clé, LDAP utilise généralement un mnémonique :

Mnnémonique Libellé Description
dn Distinguished name Nom unique dans l'arborescence
uid Userid Identifiant unique pour l'utilisateur
cn Common name Nom et prénom d'un utilisateur
givenname First name Prénom d'un utilisateur
sn Surname Nom de l'utilisateur
l Location Ville de l'utilisateur
o Organization Généralement la racine de l'annuaire (exemple : le nom de l'entreprise)
ou Organizational unit Généralement une branche de l'arbre (exemple : une division, un département ou un service)
st State Etat du pays de l'utilisateur
c Country pays de l'utilisateur
Mail Email Email de l'utilisateur

Un élément qui compose une entrée dans l'annuaire est nommé objet. Chaque objet peut contenir des attributs obligatoires ou facultatifs. Un attribut correspond à une propriété d'un objet, par exemple un email ou un numéro de téléphone pour une personne. Un attribut se présente sous la forme d'une paire clé/valeur(s).

Les classes caractérisent les objets en définissant les attributs optionnels et obligatoires qui les composent. Il existe des attributs standard communément utilisés mais il est aussi possible d'en définir d'autres.

L'ensemble des règles qui définissent l'arborescence et les attributs utilisables est stocké dans un schéma. : ce dernier permet donc de définir les classes et les objets pouvant être stockés dans l'annuaire. Un annuaire peut supporter plusieurs schémas.

Une fonctionnalité intéressante est la possibilité de pouvoir stocker des objets Java directement dans l'annuaire et de pouvoir les retrouver en utilisant le protocole LDAP. Ces objets peuvent avoir des fonctionnalités diverses telles qu'une connexion à une source de données, un objet contenant des options de paramétrage de l'application, etc ...

Un serveur LDAP propose les fonctionnalités de base suivantes :

  • Connexion/déconnexion au serveur
  • Gestion de la sécurité lors d'accès aux objets
  • Ajout, modification, suppression d'objets
  • Gestion d'attributs sur les objets
  • Recherche d'objets

 

29.6.1. L'outil OpenLDAP

Il faut télécharger OpenLDAP sur le site https://www.openldap.org/. et l'installer. La version utilisée dans cette section, est la 2.2.29.

Il faut sélectionner la langue d'installation entre anglais et allemand.

Un assistant guide l'utilisateur dans les différentes étapes de l'installation :

  • sur la page d'accueil : cliquez sur le bouton « Next »
  • sur la page « Licence Agreement » : lisez la licence et si vous l'acceptez cliquez sur le bouton radio « I accept the agreement » et cliquez sur le bouton « Next »
  • sur la page « Select Destination Location », sélectionnez le répertoire de destination et cliquez sur le bouton « Next ». Il est préférable de choisir un répertoire sans espace (exemple : C:\OpenLDAP) plutôt que le répertoire C:\Program Files\OpenLDAP proposé par défaut
  • sur la page « Select Components » : laissez la sélection par défaut et cliquez sur le bouton « Next »
  • sur la page « Select Start Menu Folder », cliquez sur le bouton « Next »
  • sur la page « Select Additional tasks », cliquez sur le bouton « Next »
  • sur la page « Ready to Install », cliquez sur le bouton « Install »
  • Les fichiers sont copiés sur le système d'exploitation
  • sur la page « Completing the OpenLDAP Setup Wizard », cliquez sur le bouton « Finish »

OpenLDAP propose en standard plusieurs schémas prédéfinis stockés dans le sous-répertoire schema.

Le fichier slapd.conf contient les principaux paramètres. Il est installé pré-paramétré dans le répertoire d'installation d'OpenLDAP (c:\openldap dans cette section).

Au début du fichier, si l'on utilise OpenLDAP avec JNDI pour stocker des objets Java, il faut ajouter le schéma Java.

Exemple :
# 
ucdata-path      ./ucdata 
include          ./schema/core.schema 
include>/b<>b<          ./schema/java.schema>/b< 
... 

Il faut ensuite configurer la base de données, le suffixe qui est la racine du serveur et le compte de l'administrateur du serveur (root).

Exemple :
#######################################################################
# BDB database definitions
#######################################################################
database              bdb
suffix                "dc=my-domain,dc=com"
rootdn                "cn=Manager,dc=my-domain,dc=com"
# Cleartext passwords, especially for the rootdn, should
# be avoid.  See slappasswd(8) and slapd.conf(5) for details.
# Use of strong authentication encouraged.
rootpw                secret
# The database directory MUST exist prior to running slapd AND 
# should only be accessible by the slapd and slap tools.
# Mode 700 recommended.
directory             ./data
# Indices to maintain
index    objectClass      eq

Il faut remplacer la valeur des clés suffix et rootdn par les valeurs appropriées au contexte.

Exemple :
suffix                "dc=test-ldap,dc=net"
rootdn              "cn=ldap-admin,dc=test-ldap,dc=net"

Pour insérer le mot de passe dans le fichier slapd.conf, il faut le crypter grâce à la commande slappasswd

Exemple :
C:\openldap>slappasswd -s ldap-admin
{SSHA}ZUPUkq7mt21rEmrFgFc0cgk9izpwL7oY

Il suffit alors de remplacer dans le fichier slapd.conf la ligne

rootpw secret

par la ligne ci-dessous qui contient le mot de passe crypté

rootpw {SSHA}ZUPUkq7mt21rEmrFgFc0cgk9izpwL7oY

Pour lancer le serveur LDAP, il suffit de double cliquer sur le fichier slapd.exe

Il ne faut pas fermer cette fenêtre dans laquelle le serveur s'exécute. Pour éviter d'avoir une fenêtre DOS ouverte, il faut utiliser le serveur en tant que service en exécutant la commande net start OpenLDAP-slapd.

Exemple :
C:\OpenLDAP>net start OpenLDAP-slapd
Le service OpenLDAP Directory Service démarre..
Le service OpenLDAP Directory Service a démarré.

Par défaut, les serveurs de type LDAP utilise le port 389 : c'est le cas pour OpenLDAP.

 

29.6.2. LDAPBrowser

Téléchargez le fichier Browser282b2.zip et le décompresser dans un répertoire du système.

Pour lancer l'application, il suffit de double cliquer sur le fichier lbe.bat.

Dans la boîte de dialogue « Connect », sélectionnez l'onglet « Quick Connect » et saisissez les informations nécessaires à la connexion.

Cliquez sur le bouton « Connect ».

Si le mot de passe n'est pas saisi, une boîte de dialogue permet de le fournir.

Il suffit alors de saisir le mot de passe défini dans le fichier slapd.conf et de cliquer sur le bouton « Connect ».

Si les informations saisies ne permettent pas de réussir la connexion, alors le message « Failed to connect » est affiché.

Si l'annuaire est vide, alors le message « List failed » est affiché

Pour initialiser l'annuaire, le plus facile est d'écrire un fichier au format LDIF (Lightweight Data Interchange Format). Ce format permet d'importer ou d'exporter des données de l'annuaire. Il permet aussi de modifier des données dans l'annuaire. Il est détaillé dans la section suivante.

Exemple : le fichier test.ldif
dn: dc=test-ldap,dc=net
objectClass: dcObject
objectClass: organization
dc: test-ldap
o: Entreprise Test
description: Entreprise de tests

dn: cn=Durand,dc=test-ldap,dc=net
objectClass: organizationalRole
cn: Durand
description: Président directeur général

Pour insérer les données du fichier test.ldif, il faut sélectionner la racine et utiliser l'option Import du menu LDIF.

Sélectionner le fichier .ldif et cliquez sur le bouton « Import ».

Les deux entrées sont affichées dans l'arborescence du serveur.

 

29.6.3. LDIF

Le format LDIF permet de réaliser des opérations d'import/export de données d'un annuaire.

La structure générale de ce format est la suivante :

Exemple :
[<id>] 
dn: <distinguished name> 
objectclass: <objectclass> 
objectclass: <objectclass> 
... 
<attribut> : <valeur>
<attribut> : <valeur> 
...

Chaque entrée est séparée dans le fichier par une ligne vide.

<id> est un entier positif facultatif qui représente un identifiant des données au niveau du serveur.

Chaque élément définit dans le fichier est séparé par une ligne vide. Il commence par son DN

Chaque attribut est définit sur sa propose ligne. La définition peut se poursuivre sur la ligne suivante si celle-ci commence par une espace ou une tabulation.

Pour fournir plusieurs valeurs à un attribut, il suffit de répéter la clé de cet attribut à raison d'une ligne pour chaque valeur.

Si la valeur d'un attribut contient des caractères non imprimables (des données binaires comme une image par exemple) alors la clé de l'attribut est suivie de :: et la valeur est encodée en base 64.

Le format LDIF permet également d'effectuer des modifications de données grâce à des opérations : add (ajouter une entrée), delete (supprimer une entrée), modrdn (modifier le rdn)

 

29.7. L'utilisation avec un annuaire LDAP

L'API JNDI permet un accès à un annuaire LDAP.

 

29.7.1. L'interface DirContext

L'interface DirContext est une classe fille de l'interface Context. Elle propose des fonctionnalités pour utiliser un service de nommage et propose en plus des fonctionnalités dédiées aux annuaires telles que la gestion des attributs et la recherche d'éléments.

Méthode Rôle
void bind( String, Object, Attributes) Associer un objet avec des attributs à un nom
void rebind(String, Object , Attributes) Redéfinir l'association d'un nom avec un objet et ses attributs
Attributes getAttributes(String) Obtenir tous les attributs de l'objet associé au nom fourni en paramètre
Attributes getAttributes(String, String []) Obtenir les valeurs des attributs listés dans le tableau en paramètre pour l'objet dont le nom est fourni
void modifyAttributes(String, int, Attributes)

Modifier les attributs de l'objet en paramètre.

L'entier permet de préciser le type de mise à jour à effectuer : ADD_ATTRIBUTE, REPLACE_ATTRIBUTE et REMOVE_ATTRIBUTE

void modifyAttributes(String, ModificationItem []) Mettre à jour des attributs dans l'ordre des éléments du tableau fourni en paramètre
NamingEnumeration search() Rechercher des entrées dans l'annuaire selon des critères fournis sous la forme d'un filtre. Il existe plusieurs surcharges de cette méthode
DirContext getSchema(String) Retourner le schéma associé à un nom

Pour pouvoir accéder à un annuaire, les étapes sont similaires à celles d'un accès à un service de nommage. Il faut obtenir une instance de type DirContext en instanciant un objet de type InitialDirContext(). Cet objet a besoin de paramètres généralement fournis sous la forme d'une collection de type Hashtable.

Ces paramètres sont les mêmes que pour un accès à un service de nommage.

 

29.7.2. La classe InitialDirContext

L'instanciation d'un objet de type InitialDirContext permet de se connecter à l'annuaire et de se positionner à un endroit précis de l'arborescence de l'annuaire nommé contexte initial.

Toutes les opérations réalisées dans l'annuaire le seront relativement à ce contexte initial.

Pour se connecter à un serveur LDAP, il faut obtenir un objet qui implémente l'interface DirContext : c'est généralement un objet de type InitialDirContext qui est obtenu en utilisant une collection de type Hashtable contenant les paramètres de connexion fournis à une fabrique dédiée.

Afin de réaliser la connexion, il est nécessaire de fournir des paramètres pour configurer son environnement. Ces paramètres sont fournis au constructeur de la classe InitialDirContext sous la forme d'un objet de type Hashtable : ces paramètres concernent plusieurs types d'informations :

  • Le fournisseur de l'implémentation
  • La localisation de l'annuaire
  • La sécurité d'accès

Deux paramètres sont obligatoires :

Context.INITIAL_CONTEXT_FACTORY  permet de préciser la classe fournie par le fournisseur
Context.PROVIDER_URL  permet de préciser une url pour localiser l'annuaire. Le format de cette url dépend du fournisseur

Exemple :
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,"ldap://localhost:389");
DirContext dircontext = new InitialDirContext(env);

Si l'accès au serveur est sécurisé, il faut fournir des paramètres supplémentaires pour permettre cette authentification : le type de sécurité utilisé, le DN d'un utilisateur et son mot de passe :

Context.SECURITY_AUTHENTICATION Permet de préciser le type de sécurité utilisé.
Les valeurs possibles sont : simple, SSL, SASL
Context.SECURITY_PRINCIPAL Permet de préciser le Distinguished Name de l'utilisateur
Context.SECURITY_CREDENTIALS Le mot de passe de l'utilisateur

LDAP supporte trois modes de sécurité :

  • Simple : pas de cryptage du DN de l'utilisateur ni de son mot de passe
  • SSL : utilisation du cryptage SSL à travers le réseau si le serveur LDAP le supporte
  • SASL : utilisation des algorithmes MD5/Kerberos
Exemple :
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class TestLDAP {

  public static void main(String[] args) {
    Hashtable env = new Hashtable();
    env
        .put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://localhost:389");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net");
    env.put(Context.SECURITY_CREDENTIALS, "inconnu");

    DirContext dirContext;

    try {
      dirContext = new InitialDirContext(env);
      dirContext.close();
    } catch (NamingException e) {
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e);
      e.printStackTrace();
    }
  }
}

Comme l'objet InitialDircontext encapsule la connexion vers l'annuaire, il est nécessaire de fermer cette connexion dès que celle-ci n'est plus utilisée en faisant appel à la méthode close().

La plupart des méthodes de la classe InitialDirContext peuvent lever une exception de type NamingException.

Si les informations de connexion au serveur sont erronées alors une exception de type javax.naming.CommunicationException est levée.

Si les informations fournies pour l'authentification sont erronées alors une exception de type javax.naming.AuthenticationException est levée avec le message «[LDAP: error code 49 - Invalid Credentials]»

A partir d'une instance de DirContext, il est possible d'accéder et de réaliser des opérations dans l'annuaire.

 

29.7.3. Les attributs

Pour manipuler les attributs d'un objet, deux interfaces existent :

  • Attributes : qui encapsule les différents attributs d'un objet
  • Attribut qui encapsule la valeur d'un attribut
Exemple :
      dirContext = new InitialDirContext(env);
      Attributes attributs = dirContext.getAttributes("cn=Dupont,dc=test-ldap,dc=net");
      Attributs attribut = (Attribut) attributs.get("description") ;
      System.out.println("Description : " + attribut.get());

Deux classes implémentent respectivement ces deux interfaces : BasicAttributes et BasicAttribut

Il est possible d'instancier une liste d'attributs par exemple pour les associer à un nouvel objet ajouté dans l'annuaire.

Exemple :
      Attributes attributes = new BasicAttributes(true);
      Attribute attribut = new BasicAttribute("telephoneNumber");
      attribut.add("99.99.99.99.99");
      attributes.put(attribut);

 

29.7.4. L'utilisation d'objets Java

La possibilité de stocker des objets Java dans un annuaire LDAP offre plusieurs intérêts :

  • Stocker des objets accessibles par plusieurs applications
  • Stocker des objets entre plusieurs exécutions d'une même application
  • Stocker des objets pour échanger des données entre plusieurs applications

A partir d'un objet de type contexte, il suffit de faire appel à la méthode bind() qui attend en paramètre un nom d'objet et un objet. Cette méthode va ajouter une entrée dans l'annuaire qui va associer le nom de l'objet à l'objet fourni en paramètre.

La méthode lookup() d'un objet de type Context permet d'obtenir un objet Java stocké dans l'annuaire à partir de son nom.

Ces deux méthodes peuvent lever une exception de type NamingException lors de leur exécution.

 

29.7.5. Le stockage d'objets Java

La plupart des annuaires permettent le stockage d'objets Java, sous réserve que l'annuaire le propose et que le schéma adéquat soit utilisé dans la configuration du serveur, ce qui n'est généralement pas le cas par défaut.

Le stockage se fait en utilisant la méthode bind() du contexte

Exemple :
import java.util.Hashtable; 
import javax.naming.Context; 
import javax.naming.NamingException; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.InitialDirContext; 

public class TestLDAP2 { 

  public static void main(String[] args) { 
    Hashtable env = new Hashtable(); 
    env.put(Context.INITIAL_CONTEXT_FACTORY, 
            "com.sun.jndi.ldap.LdapCtxFactory"); 
    env.put(Context.PROVIDER_URL, "ldap://localhost:389"); 
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net"); 
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin"); 
    DirContext dirContext; 

    try { 

      dirContext = new InitialDirContext(env); 
      MonObjet objet = new MonObjet("valeur1","valeur2"); 

      dirContext.bind("cn=monobject,dc=test-ldap,dc=net", objet); 
      dirContext.close(); 

    } catch (NamingException e) { 
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e); 
      e.printStackTrace(); 
    } 
    System.out.println("fin des traitements"); 
  } 
}

Les objets Java peuvent être stockés de différentes manières selon le serveur :

  • Stockage des objets eux-mêmes sous la forme sérialisée
  • Stockage d'une référence mémoire vers l'objet Java : cette référence est encapsulée dans un objet de type java.naming.Reference
  • Stockage des champs de l'objet sous la forme d'attributs : l'objet ainsi stocké doit obligatoirement implémenter l'interface DirContext.

L'implémentation de toutes ces méthodes est laissée libre mais le serveur doit au moins en proposer une.

Pour le stockage sous la forme sérialisée, il est nécessaire que l'objet stocké implémente l'interface java.io.Serilizable. C'est la solution la plus facile à mettre en oeuvre

Exemple :
import java.io.Serializable; 

public class MonObjet implements Serializable { 

  private static final long serialVersionUID = 3309572647822157460L; 
  private String champ1; 
  private String champ2; 

  public MonObjet() { 
    super(); 
  } 

  public MonObjet(String champ1, String champ2) { 
    super(); 
    this.champ1 = champ1; 
    this.champ2 = champ2; 
  } 

  public String getChamp1() { 
    return champ1; 
  } 

  public void setChamp1(String champ1) { 
    this.champ1 = champ1; 
  } 

  public String getChamp2() { 
    return champ2; 
  } 

  public void setChamp2(String champ2) { 
    this.champ2 = champ2; 
  } 
} 

Une exception de type java.lang.IllegalArgumentException est levée si l'objet ne respecte pas les règles permettant son ajout dans l'annuaire. Avec OpenLDAP, cette exception est levée avec le message « can only bind Referenceable, Serializable, DirContext ».

Si tout se passe bien, l'objet est ajouté dans l'annuaire sous sa forme sérialisée.

 

29.7.6. L'obtention d'un objet Java

Pour obtenir un objet stocké, il faut utiliser la méthode lookup()

Exemple :
import java.util.Hashtable; 
import javax.naming.Context; 
import javax.naming.NamingException; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.InitialDirContext; 

public class TestLDAP3 { 

  public static void main(String[] args) { 
    Hashtable env = new Hashtable(); 
    env.put(Context.INITIAL_CONTEXT_FACTORY, 
            "com.sun.jndi.ldap.LdapCtxFactory"); 
    env.put(Context.PROVIDER_URL, "ldap://localhost:389"); 
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net"); 
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin"); 
    DirContext dirContext; 

    try { 

      dirContext = new InitialDirContext(env); 
      MonObjet objet = (MonObjet) dirContext.lookup("cn=monobject,dc=test-ldap,dc=net"); 

      System.out.println("champ1="+objet.getChamp1()); 
      System.out.println("champ2="+objet.getChamp2()); 

      dirContext.close(); 
    } catch (NamingException e) { 
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e); 
      e.printStackTrace(); 
    } 
    System.out.println("fin des traitements"); 
  } 
}
Résultat :
champ1=valeur1
champ2=valeur2
fin des traitements

Si le DN fourni en paramètre de la méthode lookup ne correspond pas à celui d'un objet stocké dans l'annuaire, une exception de type javax.naming.NameNotFoundException avec le message « [LDAP: error code 32 - No Such Object] » est levée.

 

29.7.7. La modification d'un objet

La méthode modifyAttributes() de la classe DirContext permet de modifier les attributs d'un objet stocké dans l'annuaire. La méthode modifyAttributes() possède plusieurs surcharges.

Différentes opérations sont réalisables avec cette méthode en utilisant des constantes prédéfinies pour chaque type :

  • ADD_ATTRIBUTE : ajout d'un attribut
  • REMOVE_ATTRIBUTE : suppression d'un attribut
  • REPLACE_ATTRIBUTE : modification d'un attribut

Ces modifications sont soumises aux restrictions mises en place sur le serveur au niveau du schéma.

Exemple :
import java.util.Hashtable; 
import javax.naming.Context; 
import javax.naming.NamingException; 
import javax.naming.directory.Attribute; 
import javax.naming.directory.Attributes; 
import javax.naming.directory.BasicAttribute; 
import javax.naming.directory.BasicAttributes; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.InitialDirContext; 

public class TestLDAP4 { 

  public static void main(String[] args) { 
    Hashtable env = new Hashtable(); 
    env.put(Context.INITIAL_CONTEXT_FACTORY,
	  "com.sun.jndi.ldap.LdapCtxFactory"); 
    env.put(Context.PROVIDER_URL, "ldap://localhost:389"); 
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net"); 
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin"); 
    DirContext dirContext; 

    try { 

      dirContext = new InitialDirContext(env); 

      Attributes attributes = new BasicAttributes(true); 
      Attribute attribut = new BasicAttribute("telephoneNumber"); 
      attribut.add("99.99.99.99.99"); 
      attributes.put(attribut); 

      dirContext.modifyAttributes("cn=Durand,dc=test-ldap,dc=net",
	    DirContext.ADD_ATTRIBUTE,attributes); 
      dirContext.close(); 
    } catch (NamingException e) { 
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e); 
      e.printStackTrace(); 
    } 
    System.out.println("fin des traitements"); 
  } 
}

Suite à l'exécution de ce programme, l'attribut est ajouté.

Si l'attribut modifié n'est pas défini dans le schéma alors une exception de type javax.naming.directory.SchemaViolationException avec le message « [LDAP: error code 65 - attribute 'xxx' not allowed] » est levée.

Si l'attribut est ajouté alors qu'il existe déjà, une exception de type javax.naming.directory.AttributeInUseException avec le message « [LDAP: error code 20 - modify/add: xxx: value #0 already exists] » est levée.

La modification d'un attribut est similaire en utilisant le type d'opération REPLACE_ATTRIBUTE

Exemple :
import java.util.Hashtable; 
import javax.naming.Context; 
import javax.naming.NamingException; 
import javax.naming.directory.Attribute; 
import javax.naming.directory.Attributes; 
import javax.naming.directory.BasicAttribute; 
import javax.naming.directory.BasicAttributes; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.InitialDirContext; 

public class TestLDAP4 { 

  public static void main(String[] args) { 
    Hashtable env = new Hashtable(); 
    env.put(Context.INITIAL_CONTEXT_FACTORY, 
            "com.sun.jndi.ldap.LdapCtxFactory"); 
    env.put(Context.PROVIDER_URL, "ldap://localhost:389"); 
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net"); 
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin"); 
    DirContext dirContext; 

    try { 

      dirContext = new InitialDirContext(env); 

      Attributes attributes = new BasicAttributes(true); 
      Attribute attribut = new BasicAttribute("telephoneNumber"); 
      attribut.add("99.99.99.99.99"); 
      attributes.put(attribut); 

      dirContext.modifyAttributes("cn=Durand,dc=test-ldap,dc=net",
         DirContext.REPLACE_ATTRIBUTE,attributes); 
      dirContext.close(); 
    } catch (NamingException e) { 
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e); 
      e.printStackTrace(); 
    } 
    System.out.println("fin des traitements"); 
  } 
}

Suite à l'exécution de ce programme, l'attribut est modifié.

La modification d'un attribut est similaire en utilisant le type d'opération REPLACE_ATTRIBUTE

Exemple :
import java.util.Hashtable; 
import javax.naming.Context; 
import javax.naming.NamingException; 
import javax.naming.directory.Attribute; 
import javax.naming.directory.Attributes; 
import javax.naming.directory.BasicAttribute; 
import javax.naming.directory.BasicAttributes; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.InitialDirContext; 

public class TestLDAP4 { 

  public static void main(String[] args) { 
    Hashtable env = new Hashtable(); 
    env.put(Context.INITIAL_CONTEXT_FACTORY, 
            "com.sun.jndi.ldap.LdapCtxFactory"); 
    env.put(Context.PROVIDER_URL, "ldap://localhost:389"); 
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net"); 
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin"); 

    DirContext dirContext; 

    try { 
      dirContext = new InitialDirContext(env); 

      Attributes attributes = new BasicAttributes(true); 
      Attribute attribut = new BasicAttribute("telephoneNumber"); 
      attributes.put(attribut); 

      dirContext.modifyAttributes("cn=Durand,dc=test-ldap,dc=net",
         DirContext.REMOVE_ATTRIBUTE,attributes); 
      dirContext.close(); 
    } catch (NamingException e) { 
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e); 
      e.printStackTrace(); 
    } 
    System.out.println("fin des traitements"); 
  } 
}

Suite à l'exécution de ce programme, l'attribut est supprimé.

Pour réaliser plusieurs opérations, il est nécessaire d'utiliser un tableau d'objets de type ModificationItem passé en paramètre d'une version surchargée de la méthode modifyAttributes(). Dans ce cas, toutes les modifications sont effectuées ou aucune ne l'est.

Exemple :
import java.util.Hashtable; 
import javax.naming.Context; 
import javax.naming.NamingException; 
import javax.naming.directory.Attribute; 
import javax.naming.directory.BasicAttribute; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.InitialDirContext; 
import javax.naming.directory.ModificationItem; 

public class TestLDAP5 { 
  public static void main(String[] args) { 
    Hashtable env = new Hashtable(); 
    env.put(Context.INITIAL_CONTEXT_FACTORY, 
            "com.sun.jndi.ldap.LdapCtxFactory"); 
    env.put(Context.PROVIDER_URL, "ldap://localhost:389"); 
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net"); 
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin"); 

    DirContext dirContext; 
    try { 
      dirContext = new InitialDirContext(env); 

      ModificationItem[] modifItems = new ModificationItem[3]; 

      Attribute mod0 = new BasicAttribute("telephonenumber","12.34.56.78.90"); 
      Attribute mod1 = new BasicAttribute("l", "Paris"); 
      Attribute mod2 = new BasicAttribute("postalCode", "75011"); 

      modifItems[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE,mod0); 
      modifItems[1] = new ModificationItem(DirContext.ADD_ATTRIBUTE,mod1); 
      modifItems[2] = new ModificationItem(DirContext.ADD_ATTRIBUTE,mod2); 

      dirContext.modifyAttributes("cn=Durand,dc=test-ldap,dc=net", modifItems); 

      dirContext.close(); 
    } catch (NamingException e) { 
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e); 
      e.printStackTrace(); 
    } 
    System.out.println("fin des traitements"); 
  } 
}

Suite à l'exécution de ce programme, les attributs sont ajoutés.

La méthode rename() permet de modifier le DN d'une entrée de l'annuaire.

Exemple :
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class TestLDAP6 {

  public static void main(String[] args) {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://localhost:389");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net");
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin");
    DirContext dirContext;

    try {
      dirContext = new InitialDirContext(env);
      dirContext.rename("cn=Durand,dc=test-ldap,dc=net",
          "cn=Dupont,dc=test-ldap,dc=net");
      dirContext.close();
    } catch (NamingException e) {
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e);
      e.printStackTrace();
    }
    System.out.println("fin des traitements");
  }
}

Suite à l'exécution de ce programme, le DN est modifié.

Si le DN à modifier fourni en paramètre n'est pas trouvé dans l'annuaire, une exception de type javax.naming.NameNotFoundException avec le message « [LDAP: error code 32 - No Such Object] » est levée.

 

29.7.8. La suppression d'un objet

La méthode unbind() de la classe Context permet de supprimer une association entre un nom et un objet.

Exemple :
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class TestLDAP7 {

  public static void main(String[] args) {
    Hashtable env = new Hashtable();
    env
        .put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://localhost:389");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net");
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin");
    DirContext dirContext;

    try {
      dirContext = new InitialDirContext(env);
      dirContext.unbind("cn=Dupont,dc=test-ldap,dc=net");
      dirContext.close();
    } catch (NamingException e) {
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e);
      e.printStackTrace();
    }
    System.out.println("fin des traitements");
  }
}

Suite à l'exécution de ce programme, l'entrée dans l'annuaire est supprimée.

La suppression d'un contexte n'est pas autorisée s'il existe encore un seul sous-contexte. Une demande de suppression portant sur un contexte ayant encore une descendance lèvera une exception de type ContextNotEmptyException avec le message « [LDAP: error code 66 - subtree delete not supported] ».

Exemple :
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class TestLDAP8 {

  public static void main(String[] args) {
    Hashtable env = new Hashtable();
    env
        .put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://localhost:389");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net");
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin");
    DirContext dirContext;

    try {
      dirContext = new InitialDirContext(env);
      dirContext.destroySubcontext("dc=test-ldap,dc=net");
      dirContext.close();
    } catch (NamingException e) {
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e);
      e.printStackTrace();
    }
    System.out.println("fin des traitements");
  }
}

 

29.7.9. La recherche d'associations

La méthode listBindings() permet d'obtenir une liste des associations nom/objet.

Elle renvoie un objet de type NamingEnumeration qui encapsule des objets de type Binding.

Exemple :
import java.util.Hashtable;
import javax.naming.Binding;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class TestLDAP13 {

  public static void main(String[] args) {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://localhost:389");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net");
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin");
    DirContext dirContext;

    try {

      dirContext = new InitialDirContext(env);
      NamingEnumeration e = dirContext.listBindings("dc=test-ldap,dc=net");

      while (e.hasMore()) {
        Binding b = (Binding) e.next();
        System.out.println("nom    : " + b.getName());
        System.out.println("objet  : " + b.getObject());
        System.out.println("classe : " + b.getObject().getClass().getName());
      }

      dirContext.close();
    } catch (NamingException e) {
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e);
      e.printStackTrace();
    }
    System.out.println("fin des traitements");
  }
}
Exemple :
nom    : cn=monobject
objet  : MonObjet@1764be1
classe : MonObjet
nom    : cn=Durand
objet  : com.sun.jndi.ldap.LdapCtx@16fd0b7
classe : com.sun.jndi.ldap.LdapCtx
nom    : cn=Pierre
objet  : com.sun.jndi.ldap.LdapCtx@1ef9f1d
classe : com.sun.jndi.ldap.LdapCtx
nom    : cn=Martin
objet  : com.sun.jndi.ldap.LdapCtx@b753f8
classe : com.sun.jndi.ldap.LdapCtx
nom    : cn=Dupont
objet  : com.sun.jndi.ldap.LdapCtx@1e9cb75
classe : com.sun.jndi.ldap.LdapCtx

 

29.7.10. La recherche dans un annuaire LDAP

La recherche d'objets et d'informations contenues dans un objet est une des principales actions réalisées sur un annuaire.

La recherche dans un annuaire peut se faire à partir du DN d'un objet mais aussi à partir d'un ou plusieurs attributs. Cette recherche s'effectue grâce à une requête de type filtre qui possède une syntaxe particulière.

La classe DirContext propose deux fonctionnalités pour effectuer des recherches :

  • Une recherche à partir du DN
  • Une recherche à partir d'un filtre qui permet une recherche avancée (éventuellement sur plusieurs critères)

Les exemples de cette section utilisent le jeu d'essais suivant :

La méthode getAttributes() permet d'obtenir tous les attributs d'un objet à partir de son DN.

Exemple :
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;

public class TestLDAP10 {
  public static void main(String[] args) {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://localhost:389");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net");
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin");
    DirContext dirContext;

    try {
      dirContext = new InitialDirContext(env);

      Attributes attrs = dirContext.getAttributes("cn=Dupont,dc=test-ldap,dc=net");
      System.out.println("Description : " + attrs.get("description").get());

      dirContext.close();
    } catch (NamingException e) {
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e);
      e.printStackTrace();
    }
    System.out.println("fin des traitements");
  }
}
Résultat :
  Description : Directeur
  fin des traitements

Ceci impose de connaître le DN de l'objet. JNDI propose la possibilité de rechercher un ou plusieurs objets en utilisant un filtre.

Il est possible de faire une recherche sur un ou plusieurs attributs. Cette recherche se fait en utilisant la méthode search().

Deux surcharges de la méthode search permettent la recherche à partir d'attributs :

  • NamingEnumeration search(String stringName, Attributes attributesToMatch)
  • NamingEnumeration search(String stringName, Attributes attributesToMatch, String [] rgstringAttributesToReturn)

Les deux méthodes permettent de retrouver un objet dont le nom est fourni en paramètre et qui possède en plus les attributs précisés.

La seconde méthode permet aussi de préciser un tableau des attributs renvoyés dans les résultats de la recherche.

Exemple :
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchResult;

public class TestLDAP11 {
  public static void main(String[] args) {
    Hashtable env = new Hashtable();
    env.put(Context.INITIAL_CONTEXT_FACTORY,
            "com.sun.jndi.ldap.LdapCtxFactory");
    env.put(Context.PROVIDER_URL, "ldap://localhost:389");
    env.put(Context.SECURITY_AUTHENTICATION, "simple");
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net");
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin");
    DirContext dirContext;

    try {
      dirContext = new InitialDirContext(env);

      Attributes matchattribs = new BasicAttributes(true);
      matchattribs.put(new BasicAttribute("description", "Employe"));
      NamingEnumeration resultat = dirContext.search("dc=test-ldap,dc=net", matchattribs);

      while (resultat.hasMore()) {
        SearchResult sr = (SearchResult)resultat.next();
        System.out.println("Description : " + sr.getAttributes().get("cn").get());
      }

      dirContext.close();
    } catch (NamingException e) {
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e);
      e.printStackTrace();
    }
    System.out.println("fin des traitements");
  }
}
Résultat :
  Description : Pierre
  Description : Paul
  Description : Jacques
  fin des traitements

La recherche peut se faire à partir d'un filtre dont les spécifications sont définies dans la RFC 2254.

Le filtre est une expression logique qui précise les critères de recherche. La syntaxe de ce filtre est composée de conditions utilisées avec des opérateurs logiques. Un opérateur doit être précisé avant la ou les conditions sur lesquelles il agit. La syntaxe est donc de la forme :

(operateur(condition)(condition)...))
Opérateur
Condition Exemple Description
=
Egalité
(sn=test)
tous les objets dont l'attribut sn vaut test
>
Plus grand que
(sn>test)
tous les objets dont l'attribut sn est alphabétiquement plus grand que test
>=
Plus grand ou égal à
(sn>=test)
tous les objets dont l'attribut sn est alphabétiquement plus grand ou égal à test
<
Plus petit que
(sn<test)
tous les objets dont l'attribut sn est alphabétiquement plus petit que test
<=
Plus petit ou égal à
(sn<=test)
tous les objets dont l'attribut sn est alphabétiquement plus petit ou égal à test
=*
Est présent
(sn=*)
tous les objets possédant un attribut sn
*
Aucun ou plusieurs caractères quelconques
(sn=test*),
(sn=*test*),
(sn=*test)
respectivement tous les objets dont l'attribut sn commence par test, contient test ou termine par test
&
ET
(&(sn=test) (cn=test))
tous les objets dont l'attribut sn et cn valent test
|
OU
(|(sn=test) (cn=test))
tous les objets dont l'attribut sn ou cn valent test
!
NON
(!(sn=test))
tous les objets dont l'attribut sn est différent de test

Quatre autres surcharges de la méthode search() permettent de faire une recherche à partir d'un filtre.

  • NamingEnumeration search(Name name, String filterExpr, Object[] filterArgs, SearchControls cons)
  • NamingEnumeration search(String name, String filterExpr, Object[] filterArgs, SearchControls cons)
  • NamingEnumeration search(Name name, String filter, SearchControls cons)
  • NamingEnumeration search(String name, String filter, SearchControls cons)

La classe SearchControls encapsule des informations de contrôle sur la recherche à effectuer notamment :

  • searchScope : la portée de la recherche (OBJECT_SCOPE, ONELEVEL_SCOPE, SUBTREE_SCOPE)
  • countLimit : le nombre maximum d'occurrences renvoyées par la recherche
  • timeLimit : durée maximale en millisecondes de la recherche
  • returningAttributes : tableau des attributs retourné par la recherche
  • returningObjFlag : précise si les objets correspondant à la recherche sont retournés dans les résultats

Le résultat de la recherche est encapsulé dans un objet de type NamingEnumeration : cet objet est une énumération d'objets de type SearchResult.

Exemple :
import java.util.Hashtable; 
import javax.naming.Context; 
import javax.naming.NamingEnumeration; 
import javax.naming.NamingException; 
import javax.naming.directory.DirContext; 
import javax.naming.directory.InitialDirContext; 
import javax.naming.directory.SearchControls; 
import javax.naming.directory.SearchResult; 

public class TestLDAP12 { 

  public static void main(String[] args) { 
    Hashtable env = new Hashtable(); 

    env.put(Context.INITIAL_CONTEXT_FACTORY, 
            "com.sun.jndi.ldap.LdapCtxFactory"); 
    env.put(Context.PROVIDER_URL, "ldap://localhost:389"); 
    env.put(Context.SECURITY_AUTHENTICATION, "simple"); 
    env.put(Context.SECURITY_PRINCIPAL, "cn=ldap-admin,dc=test-ldap,dc=net"); 
    env.put(Context.SECURITY_CREDENTIALS, "ldap-admin"); 
    DirContext dirContext; 

    try { 
      dirContext = new InitialDirContext(env); 
      SearchControls searchControls = new SearchControls(); 
      searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); 
      NamingEnumeration resultat = dirContext.search("dc=test-ldap,dc=net", 
	    "(cn=Martin)", searchControls); 

      while (resultat.hasMore()) { 
        SearchResult sr = (SearchResult)resultat.next(); 
        System.out.println("Description : " + sr.getAttributes().get("cn").get()
           +", "+sr.getAttributes().get("description").get()); 
      } 

      dirContext.close(); 
    } catch (NamingException e) { 
      System.err.println("Erreur lors de l'acces au serveur LDAP" + e); 
      e.printStackTrace(); 
    } 
    System.out.println("fin des traitements"); 
  } 
} 
Résultat :
  Description : Martin, Chef d'equipe
  fin des traitements

Lors d'une recherche, il faut préciser le noeud de départ (base object) de la recherche et la portée de cette recherche (scope). La portée permet de définir les noeuds concernés par la recherche. Trois portées de recherche sont définies :

Portée Définition
OBJECT_SCOPE Cette portée ne concerne que le noeud de départ lui-même. Cette portée est utile pour rechercher des attributs sur un objet
ONELEVEL_SCOPE Cette portée concerne tous les noeuds d'un même niveau
SUBTREE_SCOPE C'est la portée la plus grande puisqu'elle inclut le noeud de départ et tous ses noeuds fils

OBJECT_SCOPE
ONELEVEL_SCOPE
SUBTREE_SCOPE

Exemple :
    SearchControls ctls = new SearchControls();
    ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);

Par défaut, tous les attributs des objets trouvés sont retournés. Il est possible de limiter les attributs retournés en créant un tableau des clés des attributs concernés. Il suffit alors de passer ce paramètre à la méthode setReturningAttributes() de l'instance de la classe SearchControls.

Exemple :
      String[] attributIDs = {"cn", "description"};
      searchControls.setReturningAttributes(attributIDs);

Il est possible de limiter le nombre d'objets retournés dans le résultat de la recherche. Il suffit de fournir en paramètre de la méthode setCountLimit() de l'instance de la classe SearchControls le nombre maximum d'objets retournés.

Exemple :
      searchControls.setCountLimit(1);

Attention : une exception de type javax.naming.SizeLimitExceededException avec le message «  [LDAP: error code 4 - Sizelimit Exceeded] » est levée si la limite est dépassée par le nombre d'objets trouvés.

 

29.8. JNDI et J2EE/Java EE

J2EE utilise énormément JNDI de façon implicite ou explicite notamment pour proposer des références vers des ressources nécessaires aux applications.

Chaque conteneur J2EE utilise en interne un service accessible par JNDI pour stocker des informations sur les applications et les composants. Généralement l'utilisation de JNDI dans une application J2EE se fait en utilisant ce service du conteneur.

Ces informations sont essentiellement des données de configuration : interface Home des EJB, DataSource pour accès à des bases de données, ... Ceci permet de rendre dynamique la recherche de composants de l'application.

Plusieurs technologies mises en oeuvre dans J2EE font un usage de JNDI : par exemple JDBC, EJB, JMS, ...

JDBC utilise JNDI pour stocker des objets de type DataSource qui encapsulent les informations utiles à la connexion à la source de données. Cette utilisation a été proposée à partir du package optionnel JDBC 2.0. Son utilisation n'est pas obligatoire mais elle est fortement recommandée.

Comme JDBC, JMS recommande de stocker les informations concernant les files (queues) et les sujets (topics) dans un annuaire et de les rechercher grâce à JNDI.

Les EJB stockent aussi leur référence vers leur interface home dans l'annuaire du serveur d'applications pour permette à un client d'obtenir une référence sur l'EJB.

Pour permettre de standardiser les pratiques, J2EE propose dans ses spécifications des règles de nommage pour certains objets ou composants J2EE dans l'annuaire.

 

 


[ Précédent ] [ Sommaire ] [ Suivant ] [Télécharger ]      [Accueil ]

78 commentaires Donner une note à l´article (5)

 

Copyright (C) 1999-2022 Jean-Michel DOUDOUX. Vous pouvez copier, redistribuer et/ou modifier ce document selon les termes de la Licence de Documentation Libre GNU, Version 1.1 ou toute autre version ultérieure publiée par la Free Software Foundation; les Sections Invariantes étant constitués du chapitre Préambule, aucun Texte de Première de Couverture, et aucun Texte de Quatrième de Couverture. Une copie de la licence est incluse dans la section GNU FreeDocumentation Licence. La version la plus récente de cette licence est disponible à l'adresse : GNU Free Documentation Licence.