Fecha de creación: 04.05.2007
Revisión 0.1 (04.05.2007)
Irenio Luis Chagua Aduviri
ichagua@nspsac.com
|
Cuando se está desarrollando aplicaciones empresariales, muchas veces se tiene un directorio de personas, objetos, que son necesarios tenerlos ordenado en un formato estándar, uno de ellos es LDAP (Lightweight Directory Access Protocol), pero las formas de acceso a este directorio son diversas, por eso se ha pensado en implementar el acceso desde un directorio SDK para Java utilizando un servidor web Apache Tomcat. El directorio LDAP utilizado es OpenLDAP, uno de los directorios que está al alcance del mundo del software libre.
En este artículo se presenta el uso del directorio LDAP con la finalidad de conocer con mayor profundidad, creando nuevas especificaciones de esquemas del directorio y nuevas clases de objetos con sus respectivos atributos. El mismo directorio es posible mostrar mediante un Browser LDAP en modo gráfico.
La aplicación desarrollada empieza con la creación de las entradas del directorio LDAP, configuración de la librería Netscape Directory SDK para Java, configuración de recursos JNDI con las clases JavaBean, implementación de las clases DAO para la aplicación haciendo uso del mismo directorio SDK para la búsqueda, autenticación de usuarios, creación y manipulación de las entradas de directorio y finalmente es mostrado el acceso a LDAP mediante Servlets para enviar datos a una página web haciendo uso de JSP.
Algunos de los pasos de la configuración de las herramientas se han obviado, en caso de no concerlos pueden encontrar información básica en los distintos sitios mencionados en la referencia.
LDAP (Lightweight Directory Access Protocol) o Protocolo de Acceso Ligero a Directorio es un protocolo de comunicación que permite acceder y modificar información almacenada en un directorio ordenado y distribuido en forma jerárquica, actualmente está en la versión 3. LDAP permite almacenar información de cuentas de usuario, contactos, ubicación de diversos recursos de la red, permisos de usuarios, certificados, entre otros; éste directorio está optimizado para acceso de lectura en forma eficiente y almacenar datos de poco tamaño, las modificaciones se presentan con poca frecuencia como el caso de un correo electrónico.
El protocolo LDAP accede a la información contenida en un árbol de información de directorio, las cuales están formados por un conjunto de atributos, indicando el nombre y su respectivo valor. Este directorio es único en una organización y es posible acceder de diversas aplicaciones.
El modelo de información de LDAP está basado en entradas. Una entrada es un conjunto de atributos que tienen un único Nombre Distintivo DN (Distinguished Name) y de forma global. El DN se utiliza para referirse a una entrada sin ambig?edades. Cada atributo de una entrada tiene un tipo y está asociado uno o más valores. Los tipos son normalmente palabras nemotécnicas, como "cn" para common name, o "mail" para una dirección de correo electrónico.
La sintaxis de los atributos depende del tipo de atributo. Por ejemplo, un atributo cn puede tener el valor "Mariano Apaza Quispe". Un atributo mail puede contener un valor "mapaza@nspsac.com". El atributo jpegPhoto contiene una fotografía en formato JPEG.
Los datos del directorio son un conjunto de atributos y sus respectivos valores. Por ejemplo el atributo commonName, o cn, se usa para representar el nombre de una persona.
Los datos de una persona que se registra en el directorio se definen mediante el conjunto de atributos que están definidas en las especificaciones de esquemas, en este caso la clase de objetos person.
En una entrada de directorio se presentan atributos obligatorios que deben estar presentes en la clase de objetos y atributos opcionales que no es necesario que esté presente en una entrada de clase de objetos.
Una definición de tipo de atributo especifica la sintaxis de un atributo y cómo se ordenan y comparan los atributos de ese tipo.
Los tipos de atributos en el directorio forman un árbol de clases. Por ejemplo, el tipo de atributo "commonName" es una subclase del tipo de atributo "name". Hay atributos obligatorios y opcionales que se muestran en la Tabla 1.1.
|
Identificador de Atributo |
Descripción del Valor de Atributo |
| NUMERICOID (obligatorio) | Identificador de Objeto único (OID) |
| NAME | Nombre del Atributo |
| DESC | Descripción del Atributo |
| OBSOLETE | "true" si es obsoleto; "false" o ausente si no lo es |
| SUP | Nombre del tipo de atributo superior del que se deriva el tipo de atributo |
| EQUALITY | Nombre ó OID de la regla de correspondencia si la igualdad de correspondencia está permitida; ausente si no lo está |
| ORDERING | Nombre o OID de la regla de correspondencia si está permitida la ordenación; ausente si no lo está. |
| SUBSTRING | Nombre o OID de la regla de correspondencia si está permitida la correspondencia de sub-string ausente si no lo está. |
| SYNTAX | "true" si el atributo no es multi-valor; "false" o ausente si lo es |
| COLLECTIVE | "true" si el atributo es colectivo; "false" o ausente si no lo es |
| NO-USER-MODIFICATION | "true" si el atributo no es modificable por el usuario; "false" o ausente si lo es |
| USAGE | Descripción del uso del atributo |
| Tabla 1: RFC 2252: AttributeTypeDescription |
En LDAP, una clase de objetos define el conjunto de atributos a ser usados para definir una entrada. El estándar LDAP proporciona estos tipos básicos para las clases de objetos:
person, pero también puede definirse mediante atributos en las clases de objetos inetOrgPerson, groupOfNames y organization La estructura de clases de objetos del servidor determina la lista total de atributos requeridos y permitidos para una entrada concreta.
El formato de intercambio de LDAP es un archivo de texto que almacena información de entradas de objetos en forma jerárquica. Esto nos permite importar y exportar información de directorio entre servidores de directorios basados en LDAP.
Cada entrada de directorio LDAP está organizada en un árbol de información de directorio y para identificar a alguna entrada de directorio se accede mediante su nombre distintivo (DN). Cada nombre distintivo puede estar formado por una secuencia de nombres distintivos relativos RDN (Relative Distinguished Name), como uid=mapaza. Cada nombre distintivo relativo en un DN corresponde a una rama del árbol de información de directorio. Asimismo está formado por otros atributos que lo relacionan con las jerarquías superiores como componentes de dominio DC (Domain Component), así, dc=nspsac, dc=com. Después del nombre distintivo hay una serie de atributos que definen las entradas.
Para comprender un poco sobre la estructura del directorio veamos el árbol de la gráfica donde se describe a la empresa dc=nspsac, dc=com como principal y teniendo como unidades organizacionales a Admin, People y Developer, de las cuales en cada unidad organizacional es posible agregar directorios, en este caso una cuenta de usuario con su información de datos de identificación, registro del empleado, datos de su jefe, correo electrónico, teléfono fijo, teléfono celular, la dirección en la que vive, la clave de identificación de la cuenta y la foto correspondiente del empleado.
![]() |
| Figura 1: Arbol de Directorio LDAP |
mapaza.ldif que se muestra a continuación:
dn: uid=mapaza,ou=People,dc=nspsac,dc=com givenName: Mariano sn: Apaza Quispe mail: mapaza@nspsac.com objectClass: top objectClass: person objectClass: organizationalPerson objectClass: inetOrgPerson objectClass: contribuyente uid: mapaza cn: Mariano Apaza Quispe dni: 01234567 ruc: 10012345675 employeeNumber: A214 manager: uid=jperez,ou=Admin,dc=nspsac,dc=com telephoneNumber: 051505050 mobile: 0519797979 direccion: Jr. Los Incas Nro. 6548 st: Puno l: Ilave userPassword: abc jpegPhoto:<file:///var/photo.jpgAdemás al crear un directorio de entrada los atributos pertenecen a un conjunto de clase de objetos que están definidos en una especificación de esquemas según el estándar, en este caso pertenece a la clase de objetos
person, organizationalPerson, inetOrgPerson y contribuyente, de éste último veremos más adelante.
Para almacenar estas descripciones de directorios es necesario el uso de un directorio LDAP, hay varias implementaciones de estos directorios, de distintas empresas, para distintos usos y para aplicaciones diferentes. Para el caso del presente artículo haremos uso de OpenLDAP, un directorio LDAP a nuestro alcance en el mundo del software libre.
Para descargar el instalador de OpenLDAP acceda a http://www.openldap.org/, ó si desea OpenLDAP para Windows. Una vez descargado e instalado OpenLDAP es necesario modificar el archivo de configuración slapd.conf, este archivo contiene configuración de usuario, contraseña, sufijo, dn principal, especificaciones de esquemas, entre otros. En este caso cambiaremos algunos valores de los atributos.
database bdb suffix "dc=nspsac,dc=com" rootdn "cn=root,dc=nspsac,dc=com" rootpw abc directory ./dataSe ha realizado el cambio del sufijo a "
dc=nspsac,dc=com", y dn principal a "cn=root,dc=nspsac,dc=com", luego la contraseña de la cuenta principal se ha cambiado a abc, pero también es posible colocar una contraseña encriptada con {CRYPT}, {MD5}, {SMD5}, {SSHA}, y {SHA}, si usamos alguna de estas encriptaciones la contraseña de la cuenta principal quedaría como el siguiente:rootpw {SSHA}/Wg8V59/aoeKLn4PkkKWEsdvjyz6R+/EMuchos de los atributos de la entrada de directorio descrito anteriormente no está disponible en la configuración inicial, estos atributos están basados en unas especificaciones de esquemas que delimitan su creación. Para esto quitaremos el comentario ó agregaremos si no está descrito de las siguientes especificaciones de esquemas:include ./schema/core.schema include ./schema/cosine.schema include ./schema/inetorgperson.schemaLos atributos
dni, ruc, y direccion no está definido en las especificaciones de esquemas que acabamos de activarlas, estos atributos lo he agregado para fines de conocer cómo se crea una nueva especificación de esquemas y por otro lado en mi país (Perú) todo usuario de un sistema al menos tiene su documento nacional de identidad (DNI), su registro único del contribuyente (RUC) y se agregó el atributo dirección por comodidad, aunque el atributo dni y direccion no debiera estar definido en una clase de objetos como la de contribuyente, más bien debería estar definido en la clase de objetos person, para efectos de no complicar la configuración se ha añadido en la clase de objeto contribuyente.
En una especificación de esquema se definen clases de objetos válidos que indican qué atributos debe contener en forma obligatoria y qué atributos son opcionales, así como el tipo de dato (cadenas de texto, números) de un atributo. La clase de objetos y los atributos deben estar definidos en forma global y único mediante cadenas de números (OID), para esto es necesario obtener un OID que nos permitirá crear tantas extensiones como se quiera del esquema.
Los tipos de clase de objetos que existen son tres: Structural Una clase de objeto estructural define las características básicas de un objeto. Auxiliary Una clase de objeto Auxiliar es adicional, complementa los atributos de una clase de objeto estructural. Y por último Abstract Esta clase de objeto abstracto es usado solamente para definir modelo de datos LDAP básicos.
La sintaxis básica para crear una clase de objetos es la siguiente:
objectclass ( 1.1.2.2.2 NAME 'myPerson' DESC 'Mi persona' SUP inetOrgPerson MUST ( myUniqueName $ givenName ) MAY myPhoto )Donde:
La sintaxis básica para crear un atributo es la siguiente:
attributetype ( 1.3.6.1.1.1.1.0 NAME 'uidNumber' DESC 'Identifica en forma única a un usuario' EQUALITY integerMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE )Donde:
|
Tipo de dato |
OID |
Descripción |
| Binary | 1.3.6.1.4.1.1466.115.121.1.5 | BER/DER data |
| Boolean | 1.3.6.1.4.1.1466.115.121.1.7 | boolean value |
| Distinguished Name | 1.3.6.1.4.1.1466.115.121.1.12 | DN |
| Directory String | 1.3.6.1.4.1.1466.115.121.1.15 | UTF-8 string |
| IA5String | 1.3.6.1.4.1.1466.115.121.1.26 | ASCII string |
| Integer | 1.3.6.1.4.1.1466.115.121.1.27 | Integer |
| Name and Optional UID | 1.3.6.1.4.1.1466.115.121.1.34 | DN plus UID |
| Numeric String | 1.3.6.1.4.1.1466.115.121.1.36 | Numeric String |
| OID | 1.3.6.1.4.1.1466.115.121.1.38 | Object Identifier |
| Octet String | 1.3.6.1.4.1.1466.115.121.1.40 | Arbitrary Octets |
| Printable String | 1.3.6.1.4.1.1466.115.121.1.44 | Printable String |
| Tabla 2: Sintaxis de los atributos |
|
Nombre |
Contexto |
Descripción |
| booleanMatch | equality | Boolean |
| objectIdentiferMatch | equality | OID |
| distinguishedNameMatch | equality | DN |
| uniqueMemberMatch | equality | DN with optional UID |
| numericStringMatch | equality | numerical |
| numericStringOrdering | ordering | numerical |
| numericStringSubstringsMatch | substrings | numerical |
| caseIgnoreMatch | equality | case insensitive, space insensitive |
| caseIgnoreOrderingMatch | ordering | case insensitive, space insensitive |
| caseIgnoreSubstringsMatch | substrings | case insensitive, space insensitive |
| caseExactMatch | equality | case sensitive, space insensitive |
| caseExactOrderingMatch | ordering | case sensitive, space insensitive |
| caseExactSubstringsMatch | substrings | case sensitive, space insensitive |
| caseIgnoreIA5Match | equality | case insensitive, space insensitive |
| caseIgnoreIA5OrderingMatch | ordering | case insensitive, space insensitive |
| caseIgnoreIA5SubstringsMatch | substrings | case insensitive, space insensitive |
| caseExactIA5Match | equality | case sensitive, space insensitive |
| caseExactIA5OrderingMatch | ordering | case sensitive, space insensitive |
| caseExactIA5SubstringsMatch | substrings | case sensitive, space insensitive |
| Tabla 3: Reglas de plantillas de atributos |
Regresando nuevamente a la clase de objeto contribuyente y con la base de los conceptos para crear las clases de objetos y atributos en una especificación de esquema, se define el esquema en el archivo de texto con el nombre contribuyente.schema en el directorio schema de la ruta donde está instalado OpenLDAP, con el siguiente contenido:
#
# OID prefix: 1.3.6.1.4.1.10018
#
# Attributes: 1.3.6.1.4.1.10018.1.1
#
attributetype ( 1.3.6.1.4.1.10018.1.1.1 NAME 'ruc'
DESC 'Registro único del Contribuyente'
EQUALITY numericStringMatch
SUBSTR numericStringSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{11} )
attributetype ( 1.3.6.1.4.1.10018.1.1.2 NAME 'razonSocial'
DESC 'Razón Social del Contribuyente'
SUP name )
attributetype ( 1.3.6.1.4.1.10018.1.1.3 NAME 'dni'
DESC 'Documento Nacional de Identidad de la Persona'
EQUALITY numericStringMatch
SUBSTR numericStringSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{8} )
attributetype ( 1.3.6.1.4.1.10018.1.1.4 NAME 'dniRepLegal'
DESC 'Documento Nacional de Identidad del Representante Legal del contribuyente'
EQUALITY numericStringMatch
SUBSTR numericStringSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.36{8} )
attributetype ( 1.3.6.1.4.1.10018.1.1.5 NAME 'estadoDomicilio'
DESC 'Estado del Domicilio del contribuyente'
SUP name )
attributetype ( 1.3.6.1.4.1.10018.1.1.6 NAME 'direccion'
DESC 'Domicilio de la Persona'
SUP name )
#
# Objects: 1.3.6.1.4.1.10018.1.2
#
objectclass ( 1.3.6.1.4.1.10018.1.2.1 NAME 'contribuyente'
DESC 'contribuyente'
SUP top AUXILIARY
MUST ( ruc $ dni )
MAY ( dniRepLegal $ estadoDomicilio $ razonSocial $ direccion) )
La especificación de esquema contribuyente debe ser agregado en el archivo de configuración slapd.conf la siguiente línea.include ./schema/contribuyente.schemaUna vez creado el archivo y guardado reiniciamos nuestro servicio de directorio OpenLDAP.
Antes de continuar con agregar el directorio definido anteriormente en el fichero mapaza.ldif es necesario crear las unidades organizacionales, tal como se ha visto en el árbol LDAP del gráfico, sino creamos estas unidades organizacionales no es posible agregar esta entrada de directorio. Para esto creamos un archivo de texto con el nombre ounspsac.ldif con el siguiente contenido.
dn: ou=Admin,dc=nspsac,dc=com objectClass: top objectClass: organizationalUnit ou: Admin dn: ou=People,dc=nspsac,dc=com objectClass: top objectClass: organizationalUnit ou: People dn: ou=Developer,dc=nspsac,dc=com objectClass: top objectClass: organizationalUnit ou: DeveloperPara agregar un directorio de entrada LDAP en formato LDIF ejecutaremos el siguiente comando.
slapadd -v -f slapd.conf -l ounspsac.ldifDe otra forma también es posible insertar los datos de entrada del directorio con el siguiente comando.
ldapadd -x -D "cn=root,dc=nspsac,dc=com" -w abc -f ounspsac.ldifDe modo similar ahora añadimos al directorio LDAP lo que habíamos definido inicialmente.
slapadd -v -f slapd.conf -l mapaza.ldifY así añadimos otras entradas de directorio LDAP, pero ahora la contraseña del usuario será encriptado con
{SSHA} y es de la siguiente manera.dn: uid=jmamani,ou=People,dc=nspsac,dc=com
givenName: Juan Antonio
sn: Mamani Choque
mail: jmamani@nspsac.com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: contribuyente
uid: jmamani
cn: Juan Antonio Mamani Choque
dni: 01847569
ruc: 10018475691
employeeNumber: 2097
mobile: 0519535353
direccion: Av. Titicaca Nro. 458
st: Puno
l: Juli
userPassword: {SSHA}IabIHNeVpLkbzDxCANEMj47OJ9QRh9Gj
Para generar una cadena de texto encriptada se hace uso del comando slappasswd que trae OpenLDAP de la siguiente manera:slappasswd -h {SSHA} -s abcY continuando con agregar una entrada de directorio LDAP.slapadd -v -f slapd.conf -l jmamani.ldifComo se ha visto se ha tenido que agregar en tres veces las entradas de directorio LDAP definidos, pero no es necesario realizar para cada entrada de directorio, es posible juntar las tres entradas de directorio en un solo formato LDIF denominado
nspsac.ldif.
Luego de haber insertado datos de entrada de directorio podemos hacer uso del comando slapcat para mostrar todas las entradas de directorio que se encuentra en nuestro directorio LDAP en formato LDIF, esto mismo nos puede servir para guardar estas entradas para lo que sea necesario.
Si se desea realizar una búsqueda mediante un DN es mediante el comando ldapsearch.
ldapsearch -x -b "uid=jmamani,ou=People,dc=nspsac,dc=com"Asimismo es posible eliminar entradas de directorio LDAP mediante el comando
ldapdelete.ldapdelete -x -D "cn=root,dc=nspsac,dc=com" -w abc "uid=jmamani,ou=People,dc=nspsac,dc=com"
Hasta aquí hemos visto las operaciones de entradas de directorio OpenLDAP desde líneas de comando, se puede utilizar en modo gráfico mediante un navegador de LDAP, para esto utilizaremos LDAPBrowser que está basado en Java y ejecutamos la shell lbe.bat y configuramos como la que se muestra en la figura. Con esto es posible administrar un directorio LDAP en modo gráfico.
![]() |
| Figura 2: Configuración LDAP Browser |
Lo anterior ha sido una descripción breve sobre el manejo de un directorio LDAP, pero cuando se quiere incluir el uso de directorios LDAP en aplicaciones empresariales es necesario utilizar una librería para acceder a la información de los directorios, como un lenguaje de programación. Para el presente artículo haremos uso de una librería desarrollada por Netscape, un directorio SDK para Java que también está a nuestro alcance en el mundo de software libre. Una vez descargado el código fuente y compilado según las instrucciones de cómo generar la librería de Netscape, que también pueden bajárselo una de las librerías que he compilado ldapjdk.jar será posible integrar en las aplicaciones empresariales el acceso a un directorio LDAP.
Para realizar búsquedas y localizar información en diversos sistemas como directorios LDAP es necesario una interfaz de múltiples servicios de directorio y de nombres como JNDI (Java Naming Directory Interface). JNDI nos permitirá interactuar desde Java con OpenLDAP, esto mediante un proveedor de servicios de interfaz SPI (Service Provider Interface), que en este caso es el mismo Netscape que ha desarrollado junto a la librería para el acceso a LDAP, con el nombre Service Provider LDAP que también pueden bajárselo ldapsp.jar.
Los archivos generados, una vez compilado el código fuente, específicamente del directorio packages, es necesario que los archivos JAR sean agregados a la variable de entorno CLASSPATH del sistema operativo, asumiendo que en windows se ha creado en el directorio c:\netscape\ldapjava y en FreeBSD ó UNIX en el direcotorio /usr/netscape/ldapjava, que lo llamaremos a esta ruta de directorio como <LDAPSDKHOME> Mediante los siguientes comandos agregamos a la variable de entorno.
En Linux
CLASSPATH=<LDAPSDKHOME>/packages/ldapjdk.jar:<LDAPSDKHOME>/
packages/ldapsp.jar:$CLASSPATH
export CLASSPATH
En FreeBSD, Unix
setenv CLASSPATH <LDAPSDKHOME>/packages/ldapjdk.jar:<LDAPSDKHOME>/
packages/ldapsp.jar:$CLASSPATH
En Windows
set CLASSPATH=<LDAPSDKHOME>/packages/ldapjdk.jar;<LDAPSDKHOME>/
packages/ldapsp.jar;%CLASSPATH%
Mediante el API de JNDI es posible escribir cualquier tipo de programa para acceder a información en directorios LDAP, gestores de Base de datos relacionales, servicios CORBA (COS, Corba Object Service), NDS de Novell, entre otras aplicaciones. Para que un programa de Java busque información de cualquier tipo en un directorio LDAP debe indicarse dentro del programa la ubicación del directorio LDAP mediante un Naming Manager para la ubicación física del sistema. Esto es importante ya que en cualquier momento es posible cambiar el servidor físico del directorio LDAP y no será necesario la modificación de programa fuente para luego compilarlo, sino basta con cambiar los parámetros de configuración.
![]() |
| Figura 3: API JNDI |
Para realizar una consulta a una entrada de directorio LDAP mediante la librería Netscape Directory SDK para Java, será necesario la configuración de JNDI en un servidor web, para este artículo lo voy a desarrollar en una aplicación web con Apache Tomcat. El cual trae una implementación JNDI InitialContext para cada instancia de aplicación web que se encuentre ejecutando bajo este servidor. Para esto será necesario que descarguen el instalador de Apache Tomcat y configuralo. El servidor Tomcat es una aplicación web basada en Java creada para ejecutar servlets y páginas JSP que nos ayudará para este propósito.
Para este caso se creará una aplicación web con una estructura de directorios básico, lo nombraremos ldap que se encontrará en el directorio $CATALINA_HOME/webapps Luego es necesario incluir un descriptor de la aplicación que es el archivo web.xml que contendrá la configuración de la aplicación web y estará dentro del directorio WEB-INF/ del directorio raíz de la aplicación creada, es decir $CATALINA_HOME/webapps/ldap/WEB-INF/web.xml
Las entradas InitialContext en una aplicación web son configurados en un elemento <Context> que puede estar definido en $CATALINA_HOME/conf/server.xml ó de preferencia el archivo XML del contexto de la aplicación dentro de META-INF/context.xml
Los recursos definidos en estos elementos pueden estar referidos por los siguientes elementos al utilizar la descripción de una aplicación web que se encuentra en /WEB-INF/web.xml:
<resource-env-ref> en el descriptor de la aplicación web.xml con el siguiente contenido.
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<resource-env-ref>
<description>
Objeto Factory para una instancia de BeanLDAPHost.
</description>
<resource-env-ref-name>
beanldaphost
</resource-env-ref-name>
<resource-env-ref-type>
com.nspsac.bean.BeanLDAPHost
</resource-env-ref-type>
</resource-env-ref>
</web-app>
Como habrán notado en este descriptor de la aplicación hacemos referencia a un recurso de la aplicación con el nombre beanldaphost que es de tipo de clase JavaBean com.nspsac.bean.BeanLDAPHost, que luego se implementará.
Cada recurso JNDI disponible es configurado en base a una inclusión de los siguientes elementos dentro de un elemento <Context> ó un elemento <DefaultContext>:
<Context> en el archivo de configuración $CATALINA_HOME/conf/server.xml que es el recurso que hacemos referencia con el nombre beanldaphost.
<Context path="ldap" docBase="ldap" reloadable="true" override="true"> <Resource name="beanldaphost" auth="Container" type="com.nspsac.bean.BeanLDAPHost" factory="com.nspsac.bean.BeanLDAPHostFactory" iphost="localhost" puerto="389" dnbase="dc=nspsac,dc=com" dnmgr="cn=root" dnpwd="abc" dnraiz="ou=People" ctxfactory="com.netscape.jndi.ldap.LdapContextFactory"/> <Context>En este recurso JNDI estará almacenado la información necesaria para acceder a un directorio LDAP desde una aplicación web; como habrán notado, este recurso utiliza una clase JavaBean y el Resource Factory que es una clase también JavaBean asociado mediante el atributo
factory con el valor com.nspsac.bean.BeanLDAPHostFactory, y los demás atributos (iphost, puerto, dnbase, dnmgr, dnpwd y dnraiz) de este elemento <Resource> son información que utilizaremos dentro de las clases JavaBean para realizar la conexión hacia el directorio LDAP definido anteriormente. Adicionalmente, podemos apreciar el atributo ctxfactory asociado a un proveedor de servicios de interfaz (SPI) tal como habíamos indicado anteriormente.
En esta configuración el atributo factory hace referencia a un Resource Factory, pero según la configuración de tomcat podemos incluir de la siguiente forma "org.apache.naming.factory.BeanFactory", y por razones de personalizar nuestra aplicación, se ha creado una clase JavaBean propio.
Según la configuración JNDI que se ha definido en el descriptor de la aplicación y en el Resource Factory, ahora implementamos nuestra clase JavaBean con el nombre BeanLDAPHost, esto será llamado cada vez que la aplicación se ejecute asociado a Resource Factory.
package com.nspsac.bean;
import java.io.Serializable;
import java.util.Hashtable;
public class BeanLDAPHost implements Serializable
{
private Hashtable htLdap;
public BeanLDAPHost(String iphost, int puerto,
String dnmgr, String dnpwd,
String dnbase, String dnraiz,
String ctxfactory)
{
this(iphost, Integer.toString(puerto),
dnmgr, dnpwd, dnbase, dnraiz, ctxfactory);
}
public BeanLDAPHost(String iphost, String puerto,
String dnmgr, String dnpwd,
String dnbase, String dnraiz,
String ctxfactory)
{
htLdap = new Hashtable();
htLdap.put("ldap_iphost", iphost);
htLdap.put("ldap_puerto", puerto);
htLdap.put("ldap_dnmgr", dnmgr);
htLdap.put("ldap_dnpwd", dnpwd);
htLdap.put("ldap_dnbase", dnbase);
htLdap.put("ldap_dnraiz", dnraiz);
htLdap.put("ldap_ctxfactory", ctxfactory);
}
public String getAttribute(String name) throws Exception
{
if(name == null || name.equals(""))
throw new Exception();
else
return (String)htLdap.get(name);
}
}El Resource Factory predeterminado de Apache a veces puede resultarnos limitado para ciertas operaciones, por tal razón creamos nuestro Resource Factory propio para luego integrarlo en Tomcat, la clase JavaBean Resource Factory personalizado le hemos dado el nombre de BeanLDAPHostFactory.
Para escribir la clase Resource Factory se debe implementar la interfaz del proveedor de servicio JNDI javax.naming.spi.ObjectFactory. Cada vez que la aplicación web llama al método lookup() en una entrada del contexto que está asociado a este Factory, el método getObjectInstance() es invocado con los siguientes argumentos:
package com.nspsac.bean;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.ObjectFactory;
public class BeanLDAPHostFactory implements ObjectFactory {
public Object getObjectInstance(Object obj,
Name name, Context nameCtx, Hashtable environment)
throws NamingException {
BeanLDAPHost bean = null;
Reference ref = (Reference) obj;
Enumeration addrs = ref.getAll();
String iphost = null;
int puerto = -1;
String dnbase = null;
String dnmgr = null;
String dnpwd = null;
String dnraiz = null;
String ctxfactory = null;
while (addrs.hasMoreElements()) {
RefAddr addr = (RefAddr) addrs.nextElement();
String nombre = addr.getType();
String value = (String) addr.getContent();
if (nombre.equals("iphost")) {
iphost = value;
} else if (nombre.equals("puerto")) {
try {
puerto = Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new NamingException("Invalid 'port' value " + value);
}
} else if(nombre.equals("dnbase")){
dnbase = value;
} else if(nombre.equals("dnmgr")){
dnmgr = value;
} else if(nombre.equals("dnpwd")){
dnpwd = value;
} else if(nombre.equals("dnraiz")){
dnraiz = value;
} else if(nombre.equals("ctxfactory")){
ctxfactory = value;
}
}
bean = new BeanLDAPHost(iphost,puerto,dnmgr,dnpwd,dnbase,dnraiz,ctxfactory);
return (bean);
}
}Para compilar las clases JavaBean que hemos creado es necesario que la librería que estamos utilizando del proveedor de servicios de interfaz de Netscape Directory SDK para Java ldapsp.jar esté incluido dentro del directorio de librerías de Tomcat que es $CATALINA_HOME/common/lib. Adicionalmente la librería SDK ldapjdk.jar también deberá estar incluido en este directorio de librerías de Tomcat para que funcione nuestros ejemplos de este artículo.
Para implementar los accesos a un directorio LDAP mediante una aplicación web utilizaremos el modelo DAO, que es utilizado para separar las operaciones de los datos de bajo nivel desde un nivel más alto de la lógica de negocio. Una aplicación DAO tiene los siguientes componentes:
package com.nspsac.seguridad.ldap;
import java.util.HashMap;
import javax.naming.NamingException;
import com.nspsac.utils.exception.IncompleteConversationalState;
public interface AccesoLDAP{
static final String DIR_IMGS = "../webapps/ldap/imgs/";
static final String[] ATTRS = {"dn","mail","uid","cn",
"dni","ruc","employeeNumber","jpegPhoto","manager","givenName",
"telephoneNumber","mobile","direccion","sn","st","l"};
static final String OBECT_CLASS[] = {
"top", "person", "organizationalPerson",
"inetOrgPerson", "contribuyente"};
public void getInstance() throws NamingException;
public void cambiarClave(String uid, String claveanterior, String clave)
throws NamingException;
public void addEntry(String uid, HashMap attrs);
public void renameEntry(String uid, String newUid);
public void renameEntry(String uid, String newUid, String newGroup);
public void deleteEntry(String uid);
public void addAttribute(String uid, String nameAttr, String valAttr)
throws NamingException;
public void modifyAttribute(String uid, String nameAttr, String valAttr)
throws NamingException;
public void deleteAttribute(String uid, String nameAttr)
throws NamingException;
public HashMap findByEmployeeNumber(String codPers)
throws IncompleteConversationalState;
public HashMap findByUID(String uid)
throws IncompleteConversationalState;
public HashMap autenticar(String uid, String pwd)
throws NamingException, IncompleteConversationalState;
}
package com.nspsac.seguridad.ldap;
import javax.naming.NamingException;
import com.nspsac.seguridad.ldap.AccesoLDAP;
import com.nspsac.seguridad.ldap.AccesoLDAPImpl;
public class AccesoLDAPFactory {
public static AccesoLDAP create() throws NamingException {
return new AccesoLDAPImpl();
}
}
La clase InitialContext es configurado como una aplicación web que es desplegado inicialmente, y está disponible para los componentes de la aplicación web (para acceso de solo lectura). Todas las entradas y recursos configurados están asociados al espacio de nombre JNDI java:comp/env para su acceso a un recurso. Para el caso de nuestra clase JavaBean estaría configurado como la que se muestra en el código siguiente.
package com.nspsac.seguridad.ldap;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import netscape.ldap.LDAPAttribute;
import netscape.ldap.LDAPAttributeSet;
import netscape.ldap.LDAPConnection;
import netscape.ldap.LDAPEntry;
import netscape.ldap.LDAPException;
import netscape.ldap.LDAPSearchConstraints;
import netscape.ldap.LDAPSearchResults;
import netscape.ldap.LDAPv3;
import com.nspsac.bean.BeanLDAPHost;
import com.nspsac.utils.exception.IncompleteConversationalState;
public class AccesoLDAPImpl implements AccesoLDAP{
private Hashtable htEnv;
private DirContext nspsacCtx;
public InitialDirContext getInitialDirContext() throws NamingException {
return (InitialDirContext)nspsacCtx;
}
public void getInstance() throws NamingException {
htEnv = getEnvironment();
Hashtable htJndi = new Hashtable();
htJndi.put("java.naming.factory.initial", (String)htEnv.get("DN_CTX"));
htJndi.put("java.naming.provider.url", (String)htEnv.get("DN_HOST_PORT"));
htJndi.put("java.naming.security.principal", (String)htEnv.get("DN_MGR"));
htJndi.put("java.naming.security.credentials", (String)htEnv.get("DN_PWD"));
nspsacCtx = new InitialDirContext(htJndi);
}
public Hashtable getEnvironment() throws NamingException {
InitialContext initCtx = null;
Hashtable htEnv = new Hashtable();
try {
initCtx = new InitialContext();
Context ctx = (Context)initCtx.lookup("java:comp/env");
BeanLDAPHost beanldaphost = (BeanLDAPHost)ctx.lookup("beanldaphost");
String host_port = new StringBuffer("ldap://").append(
beanldaphost.getAttribute("ldap_iphost")).append(":").append(
beanldaphost.getAttribute("ldap_puerto")).toString();
htEnv.put("DN_HOST", beanldaphost.getAttribute("ldap_iphost"));
htEnv.put("DN_PORT", beanldaphost.getAttribute("ldap_puerto"));
htEnv.put("DN_HOST_PORT", host_port);
htEnv.put("DN_MGR", beanldaphost.getAttribute("ldap_dnmgr")+","
+beanldaphost.getAttribute("ldap_dnbase"));
htEnv.put("DN_PWD", beanldaphost.getAttribute("ldap_dnpwd"));
htEnv.put("DN_BASE", beanldaphost.getAttribute("ldap_dnbase"));
htEnv.put("DN_RAIZ", beanldaphost.getAttribute("ldap_dnraiz"));
htEnv.put("DN_CTX", beanldaphost.getAttribute("ldap_ctxfactory"));
ctx.close();
}
catch(Exception e) {
e.printStackTrace();
}
finally {
if(initCtx != null)
initCtx.close();
}
return htEnv;
}
}
El método getEnvironment recogerá los valores que habíamos definido en nuestra configuración JNDI que son información acerca del directorio LDAP, esto almacenado en un tipo de dato Collection de Java que es Hashtable, para luego estar disponible la conexión hacia el directorio LDAP, el que prepara los datos de conexión hacia este directorio es el método getInstance que tendrá los datos recogidos en dos variables globales de la clase de tipo private para que pueda ser utilizado en otros métodos de la clase.
Como se ha mencionado anteriormente acerca del acceso a un directorio LDAP desde una aplicación empresarial, aquí implementaremos los métodos para el acceso, tales como autenticación de cuentas de usuario, cambio de atributos, agregar atributos, quitar atributos, buscar mediante un RDN, entre otros.
Un directorio SDK para Java tiene las siguientes funcionalidades:
Debido a que la facilidad de un directorio LDAP es su habilidad de mostrar resultados de las consultas en forma rápida, para esto empezaremos con realizar una búsqueda para obtener una entrada de nombre y sus atributos con sus respectivos valores.
Antes de realizar una búsqueda en un directorio LDAP, es necesario tener en cuenta la siguiente información:
"(&(objectclass=person)(uid=" + uid + "))"
(&(objectclass=person)(sn=Mamani*))
setBatchSize especifica cómo deben ser devueltos los resultados de la búsqueda. El valor cero '0' indica que debe esperar hasta que todos los resultados sean devueltos, el valor uno '1' devuelve cada resultado como esté disponible.
setHopLimit especifica cuántas veces deben ser devueltas en una búsqueda de una entrada.
setMaxResults especifica el número máximo de resultados que deben ser devueltas desde una búsqueda. Se usa el valor cero '0' para resultados ilimitados.
setReferrals especifica si el SDK debe ó no seguir las referencias automáticamente.
setServerTimeLimit especifica el número máximo de segundos que debe tardar en entregar los resultados de la búsqueda.
atributo=valor", que nos puede servir para realizar búsquedas por los atributos uid, employeeNumber, ruc, dni, mail, cn, sn y de los demás atributos, siempre y cuando el atributo sea de tipo cadena de texto. El método tiene el nombre de buscarEnLDAP(String uid).
Para la búsqueda, ya contamos con información del nombre del servidor, el puerto del servidor, el DN base, los atributos que están definidos en la variable ATTRS, y las preferencias de búsqueda se ha establecido a un número máximo de registro igual a 1, ya que la búsqueda está pensado para los atributos como nombres distintivos relativos y para obtener un conjunto de registros habría que cambiar estas preferencias de búsqueda.
Los resultados de una búsqueda son devueltos como un objeto LDAPSearchResults. Hay dos métodos para realizar el recorrido: nextElement y next, ambos métodos devuelven un objeto que puede ser LDAPEntry, LDAPReferralException ó LDAPException. Para este caso usaremos el método next.
El método next() de LDAPSearchResults devuelve un objeto LDAPEntry. La clase LDAPEntry contiene los siguientes cuatro métodos:
negetDN que devuelve el nombre distintivo completo de una entrada como una cadena de texto (por ejemplo, uid=jmamani, ou=People, dc=nspsac, dc=com).
getAttribute(String name) en este caso el argumento es el nombre del atributo, del que se quiere es su valor respectivo, es devuelto del tipo LDAPAttribute.
getAttributeSet devuelve un objeto LDAPAttributeSet que representa todos los atributos en esta entrada.
toString devuelve la entrada completa, incluye los atributos obtenidos como una cadena de texto.
LDAPAttribute tiene varios métodos obtener el valor de los atributos. Los métodos que normalmente se utilizan en la mayoría de los casos son los siguientes:getStringValues devuelve de tipo Enumeration los valores para un atributo de tipo cadena de texto.
getByteValues devuelve de tipo Enumeration los valores para un atributo de tipo binario, como el caso de las fotos.
getName devuelve el nombre del atributo.
private HashMap buscarEnLDAP(String uid)
throws IncompleteConversationalState {
HashMap hMap = new HashMap();
LDAPConnection ldap = new LDAPConnection();
try {
ldap.connect(3, (String)htEnv.get("DN_HOST"),
Integer.parseInt((String)htEnv.get("DN_PORT")),
(String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
LDAPSearchConstraints mySearchConstraints = ldap.getSearchConstraints();
mySearchConstraints.setMaxResults(1);
LDAPSearchResults myResults = null;
myResults = ldap.search( new StringBuffer(
(String)htEnv.get("DN_RAIZ")).append(", ").append(
(String)htEnv.get("DN_BASE")).toString(),
LDAPv3.SCOPE_SUB,
"(&(objectclass=person)(" + uid + "))",
ATTRS, false, mySearchConstraints);
if (myResults.hasMoreElements()) {
LDAPEntry myEntry = myResults.next();
hMap.put("dn", myEntry.getDN());
LDAPAttributeSet entryAttrs = myEntry.getAttributeSet();
Enumeration attrsInSet = entryAttrs.getAttributes();
String namePhoto = ((String)myEntry.getAttribute("uid").
getStringValues().nextElement()).concat(".jpg");
while (attrsInSet.hasMoreElements()) {
LDAPAttribute nextAttr = (LDAPAttribute) attrsInSet.nextElement();
String attrName = nextAttr.getName();
if (attrName.trim().equalsIgnoreCase("jpegPhoto")) {
Enumeration valsInAttr = nextAttr.getByteValues();
if (valsInAttr.hasMoreElements()) {
if(savePhoto(namePhoto, (byte[])valsInAttr.nextElement())){
hMap.put(attrName.trim(), namePhoto);
}
else {
hMap = new HashMap();
hMap.put("Mensaje","Error al recuperar Foto");
break;
}
}
}
else{
Enumeration valsInAttr = nextAttr.getStringValues();
if (valsInAttr.hasMoreElements()) {
hMap.put(attrName.trim(), valsInAttr.nextElement());
}
}
}
}
else{
hMap.put("Mensaje","El Usuario indicado no se encuentra registrado.");
}
}
catch (LDAPException e) {
e.printStackTrace();
hMap.put("Mensaje","ERROR:"
+e.getLDAPResultCode()
+"->"
+LDAPException.errorCodeToString(e.getLDAPResultCode()));
}
catch (NumberFormatException e) {
e.printStackTrace();
hMap.put("Mensaje",
"HA OCURRIDO UN PROBLEMA EN LOS PARAMETROS DEL SERVIDOR:"
+ e.getMessage());
}
finally {
try {
if (ldap.isConnected()) {
ldap.disconnect();
}
}
catch (LDAPException ex) {
throw new IncompleteConversationalState(
"NO SE PUDO CERRAR EL ENLACE:"
+ ex.getLDAPResultCode()
+ "->"
+ LDAPException.errorCodeToString(ex.getLDAPResultCode()));
}
}
return hMap;
}
private boolean savePhoto(String namePhoto, byte[] thePhoto){
boolean saved = false;
try{
OutputStream outputStream = new FileOutputStream(DIR_IMGS+namePhoto);
outputStream.write(thePhoto);
outputStream.flush();
outputStream.close();
saved = true;
}
catch (FileNotFoundException e){
e.printStackTrace();
}
catch (IOException e){
e.printStackTrace();
}
return saved;
}
Hasta aquí no hemos visto el tema de autenticación a un directorio LDAP. Las conexiones hasta aquí han sido utilizando la cuenta por defecto configurado en el mismo directorio LDAP, más no hemos utilizado la cuenta de usuario y su contraseña. Por ejemplo es posible que en un directorio se quiera restringir el acceso a ciertos atributos, no permitiendo el acceso a algunos atributos como la fotografía de un empleado, que solamente puede tener acceso personal autorizado. Para realizar cambios tales como agregar, modificar, consultar ó eliminar ciertos atributos de una entrada de directorio LDAP, por lo general se debe autenticar.
El protocolo LDAP proporciona una operación para permitir conectar a los clientes para autenticar al servidor. El método más simple de autenticación soportado por el protocolo es un método que al cliente le permite enviar un DN y contraseña al servidor. Para usar el método de autenticación simple, se puede usar mediante el método LDAPConnection.authenticate ó un método LDAPConnection.connect variante que toma una autenticación pasando como parámetros a DN y contraseña. Algunas de las excepciones que se pueden producir son las siguientes:
LDAPException.NO_SUCH_OBJECT. Esta excepción es lanzada si el DN especificado no corresponde a una entrada del directorio.
LDAPException.INVALID_CREDENTIALS. Esta excepción es lanzada si la contraseña especificado no es correcta.
public HashMap autenticar(String uid, String pwd)
throws NamingException, IncompleteConversationalState {
HashMap hMap = new HashMap();
LDAPConnection ldap = new LDAPConnection();
try {
ldap.connect(3, (String)htEnv.get("DN_HOST"),
Integer.parseInt((String)htEnv.get("DN_PORT")),
(String)htEnv.get("DN_MGR"),
(String)htEnv.get("DN_PWD"));
hMap = buscarEnLDAP("uid=" + uid);
if(hMap.get("dn") != null){
ldap.authenticate( (String) hMap.get("dn"), pwd);
}
if(hMap.get("Mensaje") == null){
hMap.put("Mensaje","Usuario ".concat(uid).concat(" autenticado"));
}
}
catch (LDAPException e) {
e.printStackTrace();
hMap = new HashMap();
switch (e.getLDAPResultCode()) {
case LDAPException.NO_SUCH_OBJECT:
hMap.put("Mensaje",uid.concat(": El usuario indicado no existe"));
break;
case LDAPException.INVALID_CREDENTIALS:
hMap.put("Mensaje",uid.concat(": Password invalido"));
break;
default:
hMap.put("Mensaje",uid.concat(
": No se ha podido realizar la autenticacion, error:"
+ e.getLDAPResultCode()));
break;
}
}
catch (NumberFormatException e) {
e.printStackTrace();
hMap = new HashMap();
hMap.put("Mensaje",
"HA OCURRIDO UN PROBLEMA EN LOS PARAMETROS DEL SERVIDOR:"
+ e.getMessage());
}
finally {
try {
if (ldap.isConnected()) {
ldap.disconnect();
}
}
catch (LDAPException ex) {
throw new IncompleteConversationalState(
"NO SE PUDO CERRAR EL ENLACE:"
+ ex.getLDAPResultCode()
+ "->"
+ LDAPException.errorCodeToString(ex.getLDAPResultCode()) + "<br>"
+ ex.getMessage());
}
}
return hMap;
}
En una entrada de directorio LDAP es posible realizar las modificaciones de una entrada, tanto en crear una entrada, modificar una entrada con sus respectivos atributos, y eliminar una entrada. En la modificación de entradas es posible el manejo de los atributos como la de añadir atributos, modificar atributos y eliminar atributos. Estas operaciones siempre estarán presentes en un manejo de entrada de directorios LDAP.
Para agregar una nueva entrada de directorio es necesario definir un nombre distintivo DN para la entrada y los atributos para esta entrada. Para agregar una nueva entrada es necesario tener en cuenta los siguientes pasos:
addEntry que recibe dos parámetros, el primero es el nombre distintivo identificador de usuario y el segundo un conjunto de atributos en un Collection HashMap.
public void addEntry(String uid, HashMap attrs) {
LDAPAttributeSet entryAttrs = new LDAPAttributeSet();
LDAPConnection ldap = new LDAPConnection();
try{
ldap.connect(3, (String)htEnv.get("DN_HOST"),
Integer.parseInt((String)htEnv.get("DN_PORT")),
(String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
String dn = new StringBuffer("uid=").append(uid).append(", ").append(
(String)htEnv.get("DN_RAIZ")).append(", ").append(
(String)htEnv.get("DN_BASE")).toString();
for(Iterator it = attrs.keySet().iterator(); it.hasNext(); ){
String name = (String)it.next();
entryAttrs.add( new LDAPAttribute( name, (String)attrs.get(name)));
}
entryAttrs.add( new LDAPAttribute("objectClass", OBECT_CLASS) );
LDAPEntry myEntry = new LDAPEntry( dn, entryAttrs );
ldap.add(myEntry);
}
catch (LDAPException e) {
e.printStackTrace();
}
}La otra operación con las entradas es la modificación de sus atributos. Para modificar un atributo de una entrada, se ha utilizado el método modifyAttributes del objeto DirContext, también es posible modificar un atributo con el uso del objeto LDAPModification y luego invocando al método modify de LDAPConnection. Para modificar los atributos de una entrada es necesario tener en cuenta los siguientes pasos:
public void addAttribute(String uid, String nameAttr, String valAttr)
throws NamingException {
ModificationItem[] mods = new ModificationItem[1];
Attribute mod = new BasicAttribute(nameAttr, valAttr);
mods[0] = new ModificationItem(DirContext.ADD_ATTRIBUTE, mod);
String dn = new StringBuffer("uid=").append(uid).append(", ").append(
htEnv.get("DN_RAIZ")).append(",").append(
htEnv.get("DN_BASE")).toString();
nspsacCtx.modifyAttributes(dn, mods);
}
public void modifyAttribute(String uid, String nameAttr, String valAttr)
throws NamingException {
ModificationItem[] mods = new ModificationItem[1];
Attribute mod = new BasicAttribute(nameAttr, valAttr);
mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod);
String dn = new StringBuffer("uid=").append(uid).append(", ").append(
htEnv.get("DN_RAIZ")).append(",").append(
htEnv.get("DN_BASE")).toString();
nspsacCtx.modifyAttributes(dn, mods);
}
public void deleteAttribute(String uid, String nameAttr)
throws NamingException {
ModificationItem[] mods = new ModificationItem[1];
Attribute mod = new BasicAttribute(nameAttr);
mods[0] = new ModificationItem(DirContext.REMOVE_ATTRIBUTE, mod);
String dn = new StringBuffer("uid=").append(uid).append(", ").append(
htEnv.get("DN_RAIZ")).append(",").append(
htEnv.get("DN_BASE")).toString();
nspsacCtx.modifyAttributes(dn, mods);
}Los datos de una entrada deben estar bien guardados en un directorio LDAP. Pero llega un momento en que será necesario borrar una entrada LDAP. Para esto quitar una entrada de un directorio es muy simple. Solamente se debe especificar qué DN se debe quitar e invocar al método LDAPConnection.delete.
public void deleteEntry(String uid) {
LDAPConnection ldap = new LDAPConnection();
try{
ldap.connect(3, (String)htEnv.get("DN_HOST"),
Integer.parseInt((String)htEnv.get("DN_PORT")),
(String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
String dn = new StringBuffer("uid=").append(uid).append(", ").append(
(String)htEnv.get("DN_RAIZ")).append(", ").append(
(String)htEnv.get("DN_BASE")).toString();
ldap.delete(dn);
}
catch (LDAPException e) {
e.printStackTrace();
}
}Por último, la operación en una entrada es el renombramiento, es decir modificando el RDN de una entrada de directorio LDAP. Por ejemplo se puede cambiar uid=pcarrillo del DN ou=People,dc=nspsac,dc=com para tener el uid=pedro.carrillo del DN ou=People,dc=nspsac,dc=com. El método LDAPConnection.rename invoca la operación LDAP para cambiar el RDN de la entrada.
No solamente es posible cambiar el RDN en un mismo nivel del árbol del directorio LDAP, hay la posibilidad de mover ó copiar a un RDN a una parte diferente del árbol del directorio LDAP. Como ejemplo veamos al RDN uid=jmamani del DN ou=People,dc=nspsac,dc=com lo movemos ó lo copiamos a otra parte del árbol como RDN uid= juan.mamani y DN ou=Admin,dc=nspsac,dc=com.
public void renameEntry(String uid, String newUid) {
LDAPConnection ldap = new LDAPConnection();
try{
ldap.connect(3, (String)htEnv.get("DN_HOST"),
Integer.parseInt((String)htEnv.get("DN_PORT")),
(String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
String dn = new StringBuffer("uid=").append(uid).append(", ").append(
(String)htEnv.get("DN_RAIZ")).append(", ").append(
(String)htEnv.get("DN_BASE")).toString();
String newRDN = new StringBuffer("uid=").append(newUid).toString();
ldap.rename(dn, newRDN, true);
}
catch (LDAPException e) {
e.printStackTrace();
}
}
public void renameEntry(String uid, String newUid, String newGroup) {
LDAPConnection ldap = new LDAPConnection();
try{
ldap.connect(3, (String)htEnv.get("DN_HOST"),
Integer.parseInt((String)htEnv.get("DN_PORT")),
(String)htEnv.get("DN_MGR"), (String)htEnv.get("DN_PWD"));
String dn = new StringBuffer("uid=").append(uid).append(", ").append(
(String)htEnv.get("DN_RAIZ")).append(", ").append(
(String)htEnv.get("DN_BASE")).toString();
String newRDN = new StringBuffer("uid=").append(newUid).toString();
String newParentDN = new StringBuffer("ou=").append(newGroup).append(", ").append(
(String)htEnv.get("DN_BASE")).toString();
ldap.rename(dn, newRDN, newParentDN, true);
}
catch (LDAPException e) {
e.printStackTrace();
}
}
Una vez que se ha realizado con la implementación de las clases para el acceso a un directorio LDAP, será necesario mostrar el resultado del acceso mediante un Servlet para enviar datos a una página web haciendo uso de JSP. Para esto creamos el Servlet ServletGestionLDAP.
package com.nspsac.seguridad;
import java.util.HashMap;
import javax.naming.NamingException;
import javax.servlet.ServletException;
import javax.servlet.ServletConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.nspsac.seguridad.ldap.AccesoLDAP;
import com.nspsac.seguridad.ldap.AccesoLDAPFactory;
import com.nspsac.utils.exception.IncompleteConversationalState;
public class ServletGestionLDAP extends HttpServlet{
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException
{
HashMap hmAut = null;
HashMap hmSearch = null;
String cambioClave = null;
HttpSession session = request.getSession(true);
try{
String accion = request.getParameter("accion");
String opcion = request.getParameter("modo");
AccesoLDAP acceso = AccesoLDAPFactory.create();
acceso.getInstance();
String usuario = request.getParameter("user");
if(accion.equals("autenticacion")){
String clave = request.getParameter("pwd");
hmAut = acceso.autenticar(usuario,clave);
session.setAttribute("hmAut", hmAut);
session.removeAttribute("hmSearch");
session.removeAttribute("cambioClave");
}
else if(accion.equals("busqueda")){
hmSearch = acceso.findByUID(usuario);
session.setAttribute("hmSearch", hmSearch);
session.removeAttribute("hmAut");
session.removeAttribute("cambioClave");
}
else if(accion.equals("cambioclave")){
String claveanterior = request.getParameter("oldpwd");
String clavenueva = request.getParameter("newpwd");
String claveconfirma = request.getParameter("repwd");
cambioClave = usuario.concat(": Usuario indicado no existe.");
if(clavenueva.equals(claveconfirma)){
acceso.cambiarClave(usuario,claveanterior,clavenueva);
cambioClave = usuario.concat(": La clave ha sido clambiada satisfactoriamente.");
}
else{
cambioClave = usuario.concat(": Clave nueva y su confirmación no concuerdan."+
"<br>Ingresar nuevamente las claves");
}
session.setAttribute("cambioClave", cambioClave);
session.removeAttribute("hmAut");
session.removeAttribute("hmSearch");
}
session.setAttribute("opcion", opcion);
HashMap hmAttrs = new HashMap();
hmAttrs.put("givenName", "Alberto");
hmAttrs.put("sn","Velasquez Prado");
hmAttrs.put("mail","avelasquez@nspsac.com");
hmAttrs.put("uid","avelasquez");
hmAttrs.put("cn","Alberto Velasquez Prado");
hmAttrs.put("dni","87654321");
hmAttrs.put("ruc","10876543218");
hmAttrs.put("employeeNumber","6825");
hmAttrs.put("telephoneNumber","051202020");
hmAttrs.put("direccion","Av. El Sol Nro. 560");
hmAttrs.put("st","Puno");
hmAttrs.put("l","Puno");
acceso.addEntry("avelasquez",hmAttrs);
acceso.addAttribute("avelasquez","userPassword","abc");
acceso.modifyAttribute("avelasquez","userPassword","abc1");
//acceso.deleteAttribute("avelasquez","userPassword");
//acceso.deleteEntry("avelasquez");
acceso.renameEntry("pcarrillo","pedro.carrillo");
acceso.renameEntry("jmamani","juan.mamani","Admin");
}
catch(NamingException ne){
ne.printStackTrace();
}
catch(IncompleteConversationalState ics){
ics.printStackTrace();
}
catch(Exception e){
e.printStackTrace();
}
try{
getServletConfig().getServletContext().getRequestDispatcher("/ldap").forward(request, response);
}
catch(Exception e){
e.printStackTrace();
}
}
}
![]() |
| Figura 4: Pantalla de Inicio |
<%
HashMap hmAut = (HashMap)session.getAttribute("hmAut");
HashMap hmSearch = (HashMap)session.getAttribute("hmSearch");
String cambioClave = (String)session.getAttribute("cambioClave");
String opcion = (String)session.getAttribute("opcion");
%>
<%//... Para mostrar los datos de una entrada después de autenticar...%>
<%if(hmAut != null){
String uid = (String)hmAut.get("uid");
String sn = (String)hmAut.get("sn");
String givenName = (String)hmAut.get("givenName");
String cn = (String)hmAut.get("cn");
String employeeNumber = (String)hmAut.get("employeeNumber");
String dni = (String)hmAut.get("dni");
String ruc = (String)hmAut.get("ruc");
String direccion = (String)hmAut.get("direccion");
String mail = (String)hmAut.get("mail");
String telephoneNumber = (String)hmAut.get("telephoneNumber");
String mobile = (String)hmAut.get("mobile");
String jpegPhoto = (String)hmAut.get("jpegPhoto");
String msg = (String)hmAut.get("Mensaje");
if(msg != null){%>
<div class="titulo"><%=msg%></div>
<%}%>
<table>
<tr><td><b>Cuenta de Usuario :</b></td> <td><%=uid%></td></tr>
<tr><td><b>Apellidos :</b></td> <td><%=sn%></td></tr>
<tr><td><b>Nombres :</b></td> <td><%=givenName%></td></tr>
<tr><td><b>Nombre Completo :</b></td> <td><%=cn%></td></tr>
<tr><td><b>Registro Empleado :</b></td>
<td><%=(employeeNumber!=null?employeeNumber:"-")%></td></tr>
<tr><td><b>DNI :</b></td> <td><%=dni%></td></tr>
<tr><td><b>RUC :</b></td> <td><%=ruc%></td></tr>
<tr><td><b>Dirección :</b></td>
<td><%=direccion%> - <%=hmAut.get("l")%>,
<%=hmAut.get("st")%></td></tr>
<tr><td><b>Correo Electrónico :</b></td> <td><%=mail%></td></tr>
<tr><td><b>Teléfono :</b></td>
<td><%=(telephoneNumber!=null)?telephoneNumber:"-"%></td></tr>
<tr><td><b>Teléfono Móvil :</b></td> <td><%=(mobile!=null)?mobile:"-"%></td></tr>
<tr>
<td valign="top"><b>Foto :</b></td>
<td>
<%if(jpegPhoto != null) {%>
<img src="imgs/<%=jpegPhoto%>" alt="" border="0" width="70" height="80">
<% } else {%>
-
<% }%>
</td>
</tr>
</table>
<%}%>
<%//... Para mostrar datos de una entrada después de una búsqueda... %>
<%if(hmSearch != null){
String msg = (String)hmSearch.get("Mensaje");
if(msg != null){%>
<div class="titulo"><%=msg%></div>
<%
hmSearch.remove("Mensaje");
}
for (Iterator it = hmSearch.keySet().iterator(); it.hasNext();){
String name = (String)it.next();
String value = (String)hmSearch.get(name);
if(!name.equals("jpegPhoto")){%>
<%=name%>: <%=value%><br>
<%}
else{
%>
<%=name%>: <br><img src="imgs/<%=value%>" alt="" border="0"><br>
<%}
}
}%>Y por último realizando las búsquedas de entradas de directorios mediante un RDN, autenticando usuarios y cambiando clave de un usuario. Solamente se ha incluido en la página web las funcionalidades mencionadas, las demás opciones ya los puede incluir con los métodos desarrollados en el presente artículo.![]() |
| Figura 5: Usuario Autenticado |
JXplorer (Java)
LDAP Browser/Editor (Java)
Luma (Unix)
Frood (Unix)
CoralDirectory LDAP Browser (Windows)
LDAP Exporter (Windows)
maX.500 (Macintosh)
phpLDAPadmin (web)
Referencias y Herramienta utilizadas
[1] OpenLDAP 2.2.29, http://www.openldap.org/
[2] Netscape Directory SDK 4.1 for Java, http://www.mozilla.org/
[3] Java Standard Edition 6 Development Kit, http://java.sun.com/
[4] Apache Jakarta Tomcat 5.5.9, http://www.apache.org/
[5] LDAP Browser\Editor 2.8.2, http://www.iit.edu/~gawojar/ldap/
[6] LDAP en Español, http://www.ldap-es.org/
[7] Fuente de este artículo
Irenio Luis Chagua Aduviri
Irenio trabaja en NetSolutions Perú S.A.C. En sus ratos libres, colabora con javaHispano y desarrolla software.
Etiquetas: j2ee
Hola,
muy buen articulo. Estoy interesado concretamente en autentificar contra un active directory de windows, de forma que los usuarios puedan usar su pasword de windows en mi aplicacion. ¿Es un ldap estandard y se puede utilizar cualquier libreria? o tiene particularidades o incompatibilidades?
Si alguien lo ha probado y tiene algun consejo o link, lo agradeceria mucho.
Saludos,
victor
Escribe tu comentario