LDAP (Lightweight Directory Access Protocol) è il protocollo standard per l'interrogazione e la modifica dei servizi di directory. Un server LDAP consente di effettuare operazioni di inserzione, cancellazione ed aggiornamento dei dati ma, a differenza di un generico database, è ottimizzato per effettuare operazioni di ricerca ed accesso alle informazioni.
Lo scopo di questo articolo, non è spiegare vantaggi e svantaggi di tale protocollo ma, analizzare gli strumenti messi a disposizione da Java per interagire con un server LDAP.
Le informazioni all'interno di un server LDAP sono organizzate in modo gerarchico in elementi chiamati entry che vengono identificati in modo univoco mediante il Distinguished Name (DN) così come un file viene identificato nel file system mediante il proprio path. Ciascuna entry viene associata ad una o più classi di oggetti (objectClass) che definiscono la struttura che questa deve assumere, attributi opzionali e obbligatori, e tipologia di informazioni contenute.
Installazione e configurazione di OpenLDAP
OpenLDAP è un'implementazione Open Source del protocollo LDAP. È possibile scaricare i sorgenti dal sito ufficiale oppure, sul sito di Lucas Bergmans, è disponibile un pacchetto di installazione per sistemi Windows (file exe).
Dopo aver installato l'applicazione, è necessario impostare alcuni parametri nel file di configurazione slapd.conf presente nella directory d'installazione.
Includere i seguenti schemi fondamentali per l'esecuzione dei nostri esempi:
- inetorgperson.schema nel quale vengono definiti diversi attributi relativi agli utenti;
- cosine.schema che è richiesto da inetorgperson;
- java.schema che permette di mappare gli oggetti java nel server LDAP.
include ./schema/cosine.schema
include ./schema/inetorgperson.schema
include ./schema/java.schema
Settare i seguenti parametri:
- suffix che identifica il dominio per il quale il server LDAP fornisce le informazioni;
- rootdn e rootpw che rappresentano rispettivamente username e password dell'utente root del server LDAP;
- directory che definisce la directory del file system nella quale vengono memorizzate le informazioni.
suffix "dc=sportfantasy,dc=org"
rootdn "cn=Manager,dc=sportfantasy,dc=org"
rootpw html
directory ./data
A questo punto è possibile avviare il server con il comando "slapd -d 1
".
Interagire con il server LDAP da Java
La SUN ha sviluppato i package javax.naming
e javax.naming.directory
che contengono delle classi utilissime per interagire con il server LDAP.
La prima operazione è la connessione al sistema. Per effettuare tale operazione, bisogna ottenere un'istanza della classe DirContext che mette a disposizione i metodi per inserire, modificare e ricercare le informazioni dal server LDAP. Per istanziare un oggetto DirContex viene utilizzata la classe InitialDirContext il cui costruttore riceve in input l'elenco delle proprietà necessarie per effettuare la connessione.
INITIAL_CONTEXT_FACTORY - driver di connessione al server
PROVIDER_URL - url del servizio
SECURITY_PRINCIPAL - usrname dell'utente manager corrispondente al rootdn definito nel file di configurazione
SECURITY_CREDENTIALS - password dell'utente manager corrispondente al rootpw definito nel file di configurazione
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL,"ldap://localhost:389");
env.put(Context.SECURITY_PRINCIPAL,"cn=Manager,dc=mycompany,dc=com");
env.put(Context.SECURITY_CREDENTIALS,"secret");
DirContext ctx = new InitialDirContext(env);
Inizializzazione dell'ambiente
Per creare delle nuove entry nel nostro server LDAP è necessario inizializzarlo associando alla root entry gli schemi domain, dcObject e top e, aggiungendo la directory "ou=utenti,dc=sportfantasy,dc=org
" nella quale aggiungeremo gli utenti.
Creare il seguente file di testo e salvarlo con estensione ldif.
dn: dc=sportfantasy,dc=org
dc: sportfantasy
objectClass: domain
objectClass: dcObject
objectClass: top
dn: ou=utenti,dc=sportfantasy,dc=org
objectClass: top
objectClass: organizationalUnit
ou: utenti
È possibile importare il file utilizzando un comando messo a disposizione da openLDAP oppure, utilizzando uno dei numerosi client free disponibili. Ad esempio è possibile scaricare LDAPAdmin, un tool di gestione rilasciato su licenza GPL.
ldapadd -x -D "cn=Manager,dc=sportfantasy,dc=org" -W -f init.ldif
Creazione di un utente
Un utente memorizzato in un server LDAP deve obbligatoriamente essere associato a tre schemi: top, person e inetOrgPerson. Naturalmente questi schemi definiscono gli attributi opzionali e obbligatori. Per semplicità inseriamo esclusivamente quelli obbligatori.
L'utente viene creato utilizzando in questo caso il metodo createSubcontext
della classe SubContext che riceve in ingresso il DN della nuova entry e la lista degli attributi con i rispettivi valori. Nel caso in cui il DN è già presente viene sollevata un'eccezione (NameAlreadyBoundException).
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389");
env.put(Context.SECURITY_PRINCIPAL, "cn=Manager,dc=sportfantasy,dc=org");
env.put(Context.SECURITY_CREDENTIALS, "html");
try {
DirContext ctx = new InitialDirContext(env);
//Creazione attributi utente:
Attribute objClasses = new BasicAttribute("objectclass");
objClasses.add("top");
objClasses.add("person");
objClasses.add("inetOrgPerson");
Attribute cn = new BasicAttribute("cn", "pippo");
Attribute sn = new BasicAttribute("sn", "pippo");
Attribute uid = new BasicAttribute("uid", "pippo");
Attribute userPassword = new BasicAttribute("userPassword", "pippo");
Attributes attributi = new BasicAttributes();
attributi.put(objClasses);
attributi.put(cn);
attributi.put(sn);
attributi.put(uid);
attributi.put(userPassword);
//creazione dell'utente
ctx.createSubcontext("cn=pippo,ou=utenti,dc=sportfantasy,dc=org", attributi);
System.out.println("L'utente è stato creato <<cn=pippo,ou=utenti,dc=sportfantasy,dc=org>>");
}
catch (NameAlreadyBoundException nabe){
System.err.println("Il DN immesso è già esistente!");
nabe.printStackTrace();
}
catch (Exception e){
e.printStackTrace();
}
Ricerca degli attributi di un utente
Mediante il metodo getAttributes
, della classe DirContext, leggiamo gli attributi di una entry. Il metodo riceve in ingresso il DN della directory e l'elenco degli attributi da estrarre. Ciascun attributo può avere n valori quindi occorre un ciclo for
per stamparli.
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389");
env.put(Context.SECURITY_PRINCIPAL, "cn=Manager,dc=sportfantasy,dc=org");
env.put(Context.SECURITY_CREDENTIALS, "html");
try{
DirContext ctx = new InitialDirContext(env);
String[] attrs = new String[4];
attrs[0] = "cn";
attrs[1] = "sn";
attrs[2] = "uid";
attrs[3] = "userPassword";
Attributes result = ctx.getAttributes("cn=pippo,ou=utenti,dc=sportfantasy,dc=org", attrs);
if (result == null){
System.out.print("Attributi non presenti");
}else{
Attribute attr = result.get("cn");
if (attr != null){
System.out.println("cn:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
attr = result.get("sn");
if (attr != null){
System.out.println("sn:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
attr = result.get("uid");
if (attr != null){
System.out.println("uid:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
attr = result.get("userPassword");
if (attr != null){
System.out.println("userPassword:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
}
}
catch(NamingException ne){
ne.printStackTrace();
}
Ricerca di tutti gli utenti memorizzati nel server LDAP
È possibile ricercare le entry all'interno del server LDAP semplicemente specificando il punto di partenza per la ricerca ed, eventualmente, lo scope (soltanto nel livello corrente oppure in tutti i sottolivelli rispetto a quello corrente). Inoltre è possibile specificare un filtro di ricerca in modo analogo a come avviene per le query SQL, come se fosse una where condition. Un utente è contraddistinto dai seguenti valori del suo attributo objectClass: person e inetOrgPerson quindi, utilizziamo come filtro di ricerca (&(objectClass=inetOrgPerson)(objectClass=person))
.
La ricerca viene effettuata con il metodo search della classe DirContext che riceve in ingresso il punto di partenza per la ricerca, il filtro e gli attributi settabili mediante la classe SearchControls. Nel nostro caso abbiamo semplicemente settato lo scope ma, è possibile impostare anche altri attributi, come ad esempio il numero massimo di elementi (ctls.setCountLimit(100)
) e gli attributi da restituire (ctls.setReturningAttributes(new String[] {"cn", "uid"})
).
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389");
env.put(Context.SECURITY_PRINCIPAL, "cn=Manager,dc=sportfantasy,dc=org");
env.put(Context.SECURITY_CREDENTIALS, "html");
try{
DirContext ctx = new InitialDirContext(env);
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
String filter = "(&(objectClass=inetOrgPerson)(objectClass=person))";
NamingEnumeration<SearchResult> answer = ctx.search("dc=sportfantasy,dc=org", filter, ctls);
while (answer.hasMoreElements()){
SearchResult a = answer.nextElement();
System.out.println(a.getNameInNamespace());
Attributes result = a.getAttributes();
if (result == null){
System.out.print("Attributi non presenti");
}else{
Attribute attr = result.get("cn");
if (attr != null){
System.out.println("cn:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
attr = result.get("sn");
if (attr != null){
System.out.println("sn:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
attr = result.get("uid");
if (attr != null){
System.out.println("uid:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
attr = result.get("userPassword");
if (attr != null){
System.out.println("userPassword:");
for (NamingEnumeration vals = attr.getAll(); vals.hasMoreElements(); System.out.println("t" + vals.nextElement()));
}
}
}
}
catch(NamingException ne){
ne.printStackTrace();
}
Naturalmente gli esempi proposti sono validi per qualsiasi server LDAP e non soltanto per OpenLDAP.