Spring Ldap: operaciones básicas haciendo uso del soporte de plantillas

0
13428

Spring Ldap: operaciones básicas haciendo uso del soporte de plantillas.

0. Índice de contenidos.


1. Introducción

Como de costumbre, ya habíamos empezado por los tests, exponiendo hace tiempo
cómo ejecutar tests de integración contra un servidor ldap embebido con el soporte de Apache DS
, y ahora, en este tutorial, vamos
a ponerlos en verde, implementando el soporte de los servicios necesarios para acceder al contenido de nuestro directorio
de información con el soporte de plantillas para ldap de Spring.

Spring Ldap es un proyecto de Spring Source que simplifica
la programación contra ldap, basándose en los mismos principios que la plantillas JDBC de Spring. Las principales características del proyecto son:

  • elimina la necesidad de preocuparnos por abrir/cerrar la conexión y los recursos,
  • elimina la necesidad de realizar las iteraciones de los resultados sobre NamingEnumeration,
  • proporciona una jerarquía de excepciones unchecked, basada en DataAccessException de Spring,
  • proporciona utilidades para generar de forma dinámica filtros y DNs y facilita la gestión de atributos,
  • proporciona una gestión de transacciones en el lado del cliente, y
  • permite configurar un pool de conexiones para reutilizar las conexiones físicas abiertas.

En este tutorial vamos a dar nuestros primeros pasos, viendo los beneficios de su uso en
comparación con el api de JNDI y realizando las primeras operaciones contra el directorio.


2. Entorno.

El tutorial, y el código que contiene, han sido escritos usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15′ (2.3 GHz Intel Core i7, 16GB DDR3).
  • Sistema Operativo: Mac OS Mavericks 10.9.2
  • Eclipse Kepler + m2e plugin
  • Spring Ldap 2.0.1


3. Comparación con el API de JNDI.

El soporte de ldap que nos proporciona Spring está basado en el uso del
patrón plantilla y la mejor forma de ver sus
beneficios es exponer primero todas aquellas operaciones que se encapsulan dentro de la plantilla.

Spring ldap template es a JNDI, lo que String JDBC template es a JDBC, si no tuviéramos el soporte de Spring, para
realizar una operación de consulta contra ldap podríamos hacer uso de JNDI con un código como el siguiente:

public class TraditionalPersonDaoImpl implements PersonDao {
   public List getAllPersonNames() {
      Hashtable env = new Hashtable();
      env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
      env.put(Context.PROVIDER_URL, "ldap://localhost:389/dc=example,dc=com");
      DirContext ctx;
      try {
         ctx = new InitialDirContext(env);
      } catch (NamingException e) {
         throw new RuntimeException(e);
      }
      LinkedList list = new LinkedList();
      NamingEnumeration results = null;
      try {
    SearchControls controls = new SearchControls(); 
    controls.setSearchScope(SearchControls.SUBTREE_SCOPE); 
    results = ctx.search("", "(objectclass=person)", controls);
    
    while (results.hasMore()) {
      SearchResult searchResult = (SearchResult) results.next(); 
      Attributes attributes = searchResult.getAttributes(); 
      Attribute attr = attributes.get("cn");
      String cn = (String) attr.get();
      list.add(cn);
         }
      } catch (NameNotFoundException e) {
         // The base context was not found.
         // Just clean up and exit.
      } catch (NamingException e) {
         throw new RuntimeException(e);
      } finally {
         if (results != null) {
            try {
               results.close();
            } catch (Exception e) {
               // Never mind this.
            }
         }
         if (ctx != null) {
            try {
               ctx.close();
            } catch (Exception e) {
               // Never mind this.
      } 
    }
    }
      return list;
  } 
}

Los principales incovenientes de JNDI son:

  • Gestión de recursos explícita:
    • el desarrollador es responsable de cerrar todos los recursos,
    • es propenso a errores y puede dar lugar a pérdidas de memoria.
  • Boilerplate code:
    • todos los métodos basados en JNDI tendrán gran cantidad de «plumbing code» que se puede extraer y reutilizar fácilmente.
  • Checked Exceptions
    • obliga a manejar explícitamente NamingException

El código anterior, desde el punto de vista de Spring Ldap, se podría reducir al siguiente:

public class PersonDaoImpl implements PersonDao {
   private LdapTemplate ldapTemplate;
   public void setLdapTemplate(LdapTemplate ldapTemplate) {
      this.ldapTemplate = ldapTemplate;
   }

   public List getAllPersonNames() { 
     return ldapTemplate.search(
        "", "(objectclass=person)", 
        new AttributesMapper() {
          public Object mapFromAttributes(Attributes attrs) throws NamingException {
            return attrs.get("cn").get();
      } 
    }
   });
  }
} 

El patrón plantilla nos abstrae de las siguientes operaciones que son comunes para todas las conexiones:

  • obtener la conexión
  • participar de la transacción, si la hubiera
  • ejecutar la consulta
  • procesar el resultado de la búsqueda
  • manejar las excepciones
  • liberar la conexión

Claro que aquí estamos dando por supuesto que la configuración de la plantilla se realiza por inversión de control
con el soporte de la inyección de dependencias de Spring.

<beans>
   <bean id="contextSource"
 class="org.springframework.ldap.core.support.LdapContextSource">
      <property name="url" value="ldap://localhost:389" />
      <property name="base" value="dc=example,dc=com" />
      <property name="userDn" value="cn=Manager" />
      <property name="password" value="secret" />
   </bean>
   <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
      <constructor-arg ref="contextSource" />
</bean>
   <bean id="personDao" class="com.example.dao.PersonDaoImpl">
      <property name="ldapTemplate" ref="ldapTemplate" />
   </bean>
</beans>

No obstante lo anterior, si no tuviesemos el soporte de Spring también podríamos
configurarla programáticamente y haciendo uso de los mismos patrones creacionales de Spring disponer de una instancia
única de nuestro servicio.


4. Implementando un CRUD.

Una vez vistos los beneficios y cómo configurar nuestra plantilla vamos a implementar nuestros primeros métodos
de acceso a ldap.


4.1. Consulta.

Para realizar una consulta solo tendremos que hacer uso de la plantilla programando el mapeo de los resultados implementando una interfaz.

public UserAccount getByUid(String uid) {
        final SearchControls searchControls = new SearchControls();  
        searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE); 
        final AndFilter andFilter = new AndFilter(); 
        andFilter.and(new EqualsFilter(OBJECTCLASS_FIELD , "person")); 
        andFilter.and(new EqualsFilter("uid", uid)); // avoid ldap injection
        final List<UserAccount> usuarios = ldapTemplate.search("ou=users,o=autentia", andFilter.encode() ,  
                                                                searchControls, getUserAccountAttributesMapper()); 
        if (usuarios.isEmpty()) { 
          throw new IllegalArgumentException(uid + " not found!"); 
        } 
        return usuarios.get(0); 
    }

    AttributesMapper<UserAccount> getUserAccountAttributesMapper() { 
        return new UserAccountAttributesMapper(); 
    } 

   class UserAccountAttributesMapper implements AttributesMapper<UserAccount>{

  @Override
  public UserAccount mapFromAttributes(Attributes attributes)
      throws NamingException {
    final UserAccount userAccount = new UserAccount();
    
    Attribute attribute = attributes.get("uid");
    if (attribute != null){
      userAccount.setLogin((String)attribute.get()); 
    }
    attribute = attributes.get("mail");
    if (attribute != null){
      userAccount.setEmail((String)attribute.get()); 
    }
    // TODO more mappings
    return userAccount;
  }

El soporte de Spring ldap también nos permite ahorrar dicho mapeo añadiendo anotaciones con la metainformación en las
propiedades de nuestras clases de negocio sobre qué atributos mapean en los objetos ldap, de este modo es cómo si
trabajásemos con anotaciones de JPA.


4.2. Bind.

Si en el punto anterior, para realizar una consulta, implementábamos un mapper, ahora para realizar operaciones
de persistencia tendremos que implementar un método builder, aunque en este caso, no haya una interfaz que seguir:

@Override
public void save(UserAccount userAccount) {
    LdapName dn;
    try {
        dn = new LdapName(buildDN(userAccount));
    } catch (InvalidNameException e) {
        throw new IllegalArgumentException(e);
    }
    ldapTemplate.bind(dn, null, createAttributes(userAccount));
}

private String buildDN(UserAccount userAccount) {
  return "uid="+userAccount.getLogin()+",ou=users,o=autentia";
}

private Attributes createAttributes(UserAccount userAccount) {
    final Attributes attributes = new BasicAttributes();
    attributes.put(new BasicAttribute("objectClass", "inetOrgPerson")); 
    attributes.put(new BasicAttribute("uid", userAccount.getLogin())); 
    attributes.put(new BasicAttribute("cn", userAccount.getName()));
    attributes.put(new BasicAttribute("sn", userAccount.getSurname()));
    attributes.put(new BasicAttribute("mail", userAccount.getEmail()));
    return attributes;
}


4.3. Unbind.

La última operación que veremos en este tutorial será la de borrado, que es tan simple como implementar un código
como el siguiente:

@Override
public void delete(UserAccount userAccount) {
    ldapTemplate.unbind(buildDN(userAccount));
}

private String buildDN(UserAccount userAccount) {
  return "uid="+userAccount.getLogin()+",ou=users,o=autentia";
}


5. Referencias.


6. Conclusiones.

Como con muchos otros aspectos de nuestras tareas de programación diarias, Spring está ahí, cerca, para hacernos
la vida más fácil, gracias a su arquitectura basada en patrones de diseño.

Un saludo.

Jose

jmsanchez@autentia.com

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad