Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |
|||||||
Niveau : | 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 :
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 :
JNDI est composée de deux parties
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 :
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.
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.
Pour permettre une abstraction des différents formats de noms utilisés par les différents services, JNDI utilise la classe Name.
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 :
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, ...)
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 :
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)
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 |
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 :
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 :
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 :
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.
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 :
Toutes les opérations possèdent deux versions surchargées attendant respectivement :
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.
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");
}
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.
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.
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 |
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 :
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 :
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.
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.
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)
L'API JNDI permet un accès à un annuaire LDAP.
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.
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 :
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é :
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.
Pour manipuler les attributs d'un objet, deux interfaces existent :
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);
La possibilité de stocker des objets Java dans un annuaire LDAP offre plusieurs intérêts :
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.
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 :
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.
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.
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 :
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.
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");
}
}
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
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 :
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 :
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*), |
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.
La classe SearchControls encapsule des informations de contrôle sur la recherche à effectuer notamment :
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.
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.
Développons en Java v 2.40 Copyright (C) 1999-2023 Jean-Michel DOUDOUX. |