CHAPTER 10

image

Odds and Ends

In this chapter, you will learn

  • How to perform authentication using Spring LDAP
  • How to parse LDIF files
  • LDAP connection pooling

Authentication Using Spring LDAP

Authentication is a common operation performed against LDAP servers. This usually involves verifying a username and password against the information stored in the directory server.

One approach for implementing authentication using Spring LDAP is via the getContext method of the ContextSource class. Here is the getContext method API:

DirContext getContext(String  principal, String credentials) throws  NamingException

The principal parameter is the fully qualified DN of the user, and the credentials parameter is the user’s password. The method uses the passed-in information to authenticate against LDAP. Upon successful authentication, the method returns a DirContext instance representing the user’s entry. Authentication failures are communicated to the caller via an exception. Listing 10-1 gives a DAO implementation for authenticating patrons in your Library application using the getContext technique.

Listing 10-1.

package com.inflinx.book.ldap.repository;
 
import javax.naming.directory.DirContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ldap.NamingException;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.support.LdapUtils;
import org.springframework.stereotype.Repository;
 
@Repository("authenticationDao")
public class AuthenticationDaoImpl implements AuthenticationDao{
 
   public static final String BASE_DN = "ou=patrons,dc=inflinx,dc=com";
            
   @Autowired
   @Qualifier("contextSource")
   private ContextSource contextSource;
 
   @Override
   public boolean authenticate(String userid, String password) {
      DistinguishedName dn = new DistinguishedName(BASE_DN);
      dn.add("uid", userid);
      DirContext authenticatedContext = null;
      try {
         authenticatedContext = contextSource.getContext( dn.toString(), password);
         return true;
      }
      catch(NamingException e) {
         e.printStackTrace();
         return false;
      }
      finally {
         LdapUtils.closeContext(authenticatedContext);
      }
   }
}

The getContext method requires a fully qualified DN of the user entry. Hence, the authentication method starts out by creating a DistinguishedName instance with the supplied "ou=patrons,dc=inflinx,dc=com" base. Then you append the provided userid to the DN to create the patron’s fully qualified DN. The authentication method then invokes the getContext method, passing in the string representation of the patron’s DN and password. A successful authentication simply exits the method with a return value of true. Notice that in the finally block you are closing the obtained context.

Listing 10-2 shows a JUnit test to verify the proper working of this authenticate method.

Listing 10-2.

package com.inflinx.book.ldap.parser;
 
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.inflinx.book.ldap.repository.AuthenticationDao;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:repositoryContext-test.xml")
 
public class AuthenticationDaoTest {
 
@Autowired
@Qualifier("authenticationDao")
private AuthenticationDao authenticationDao;
 
   @Test
   public void testAuthenticate() {
      boolean authResult = authenticationDao.authenticate("patron0", "password");
      Assert.assertTrue(authResult);
      authResult = authenticationDao.authenticate("patron0", "invalidPassword");
      Assert.assertFalse(authResult);
   }
}

The repositoryContext-test.xml associated with Listing 10-2 is shown in Listing 10-3. In this scenario, you are working with your installed OpenDJ LDAP server.

Listing 10-3.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
                
   <context:component-scan base-package="com.inflinx.book.ldap" />
 
   <bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
      <property name="url" value="ldap://localhost:11389" />
      <property name="userDn" value="cn=Directory Manager" />
      <property name="password" value="opendj" />
      <property name="base" value=""/>
   </bean>
   <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
      <constructor-arg ref="contextSource" />
   </bean>
</beans>

The only drawback with the implementation shown in Listing 10-3 is that the getContext method requires the fully qualified DN of the patron entry. There could be scenarios where the client’s code might not know the fully qualified DN of the user. In Listing 10-1, you append a hard-coded value to create the fully qualified DN. This approach will fail if you want to start using the code in Listing 10-1, say, to authenticate your library’s employees also. To address such situations, Spring LDAP added several variations of the authenticate method shown below to the LdapTemplate class:

boolean authenticate(String base, String filter,  String password)

This authenticate method uses the supplied base DN and filter parameters to perform a search for the user’s LDAP entry. If an entry is found, the fully qualified DN of the user is extracted. Then, this DN, along with the password, is passed to the ContextSource’s getContext method to perform authentication. Essentially this is a two-step process but it alleviates the need for fully qualified DN upfront. Listing 10-4 contains the modified authentication implementation. Notice that the authenticate method signature in the DAO implementation has not changed. It still accepts the username and password as its parameters. But thanks to the authenticate method abstraction, the implementation has become lot simpler. The implementation passes an empty base DN since you want the search to be performed relative to the base DN used during ContextSource creation.

Listing 10-4.

package com.inflinx.book.ldap.repository;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Repository;
 
@Repository("authenticationDao2")
public class AuthenticationDaoImpl2 implements AuthenticationDao {
 
   @Autowired
   @Qualifier("ldapTemplate")
   private LdapTemplate ldapTemplate;
 
   @Override
   public boolean authenticate(String userid, String password){
      return ldapTemplate.authenticate("","(uid=" + userid + ")", password);
   }
}

Listing 10-5 shows the JUnit test case to verify the above authenticate method implementation.

Listing 10-5.

package com.inflinx.book.ldap.parser;
 
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.inflinx.book.ldap.repository.AuthenticationDao;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:repositoryContext-test.xml")
public class AuthenticationDao2Test {
 
@Autowired
@Qualifier("authenticationDao2")
private AuthenticationDao authenticationDao;
 
   @Test
   public void testAuthenticate() {
      boolean authResult = authenticationDao.authenticate("patron0", "password");
      Assert.assertTrue(authResult);
      authResult = authenticationDao.authenticate("patron0","invalidPassword");
      Assert.assertFalse(authResult);
   }
}

Handling Authentication Exceptions

The previous authenticate methods in LdapTemplate simply tell you whether authentication succeeded or failed. There will be cases where you are interested in the actual exception that caused the failure. For those scenarios, LdapTemplate provides overloaded versions of the authenticate method. The API for one of the overloaded authenticate methods is as follows:

boolean authenticate(String base, String filter,  String  password, AuthenticationErrorCallback errorCallback);

Any exceptions that occur during the execution of the above authenticate method will be passed on to an AuthenticationErrorCallback instance provided as the method parameter. This collected exception can be logged or used for post-authentication processes. Listing 10-6 and Listing 10-7 show the AuthenticationErrorCallback API and its simple implementation, respectively. The execute method in the callback can decide what to do with the raised exception. In your simple implementation, you are just storing it and making it available to the LdapTemplate’s search caller.

Listing 10-6.

package org.springframework.ldap.core;
 
public interface AuthenticationErrorCallback {
   public void execute(Exception e);
}

Listing 10-7.

package com.practicalspring.ldap.repository;
 
import org.springframework.ldap.core.AuthenticationErrorCallback;
 
public class EmployeeAuthenticationErrorCallback implements AuthenticationErrorCallback {
 
   private Exception authenticationException;
 
   @Override
   public void execute(Exception e) {
      this.authenticationException = e;
   }
 
   public Exception getAuthenticationException() {
      return authenticationException;
   }
}

Listing 10-8 shows the modified AuthenticationDao implementation along with the error callback; here you are simply logging the failed exception to the console. Listing 10-9 shows the JUnit test.

Listing 10-8.

package com.practicalspring.ldap.repository;
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.stereotype.Repository;
 
@Repository("authenticationDao3")
public class AuthenticationDaoImpl3 implements AuthenticationDao {
 
@Autowired
@Qualifier("ldapTemplate")
private LdapTemplate ldapTemplate;
 
   @Override
   public boolean authenticate(String userid, String password){
      EmployeeAuthenticationErrorCallback errorCallback = new EmployeeAuthenticationErrorCallback();
      boolean isAuthenticated = ldapTemplate.authenticate("","(uid=" + userid + ")", password, errorCallback);
      if(!isAuthenticated) {
         System.out.println(errorCallback.getAuthenticationException());
      }
      return isAuthenticated;
   }
}

Listing 10-9.

package com.inflinx.book.ldap.parser;
 
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.inflinx.book.ldap.repository.AuthenticationDao;
 
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:repositoryContext-test.xml")
public class AuthenticationDao3Test {
 
@Autowired
@Qualifier("authenticationDao3")
private AuthenticationDao authenticationDao;
 
   @Test
   public void testAuthenticate() {
      boolean authResult = authenticationDao.authenticate("patron0", "invalidPassword");
      Assert.assertFalse(authResult);
   }
}

Upon running the JUnit test in Listing 10-9, you should see the following error message in the console:

org.springframework.ldap.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]; nested exception is javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]

Parsing LDIF Data

The LDAP Data Interchange Format is a standards-based data interchange format for representing LDAP directory data in a flat file format. LDIF is discussed in detail in Chapter 1. As an LDAP developer or administrator, you may sometimes need to parse LDIF files and perform operations such as a bulk directory load. For such scenarios, Spring LDAP introduced a set of classes in the org.springframework.ldap.ldif package and its subpackages that make it easy to read and parse LDIF files.

Central to the org.springframework.ldap.ldif.parser package is the Parser interface and its default implementation LdifParser. The LdifParser is responsible for reading individual lines from an LDIF file and converting them into Java objects. This object representation is possible through two newly added classes, namely LdapAttribute and LdapAttributes.

The code in Listing 10-10 uses LdifParser to read and print the total number of records in an LDIF file. You start the implementation by creating an instance of LdifParser and passing in the file you would like to parse. Before the parser can be used, you need to open it. Then you use the parser’s iterator style interface for reading and counting individual records.

Listing 10-10.

package com.inflinx.book.ldap.parser;
 
import java.io.File;
import java.io.IOException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.core.LdapAttributes;
import org.springframework.ldap.ldif.parser.LdifParser;
 
public class SimpleLdifParser {
 
   public void parse(File file) throws IOException {
      LdifParser parser = new LdifParser(file);
      parser.open();
      int count = 0;
      while(parser.hasMoreRecords()) {
         LdapAttributes attributes = parser.getRecord();
         count ++;
      }
      parser.close();
      System.out.println(count);
   }
 
   public static void main(String[] args) throws IOException {
      SimpleLdifParser parser = new SimpleLdifParser();
      parser.parse(new ClassPathResource("patrons.ldif").getFile());
   }
}

Before running the above class, make sure that you have the patrons.ldif file in the classpath. Upon running the class with the patrons.ldif file included with the Chapter 10 code, you should see the count 103 printed to the console.

The parsing implementation of LdifParser relies on three supporting policy definitions: separator policy, attribute validation policy, and record specification policy.

  • The separator policy provides the separation rules for LDIF records in a file and is defined in the RFC 2849. It is implemented via the org.springframework.ldap.ldif.support.SeparatorPolicy class.
  • The attribute validation policy, as the name suggests, is used to ensure that all the attributes are structured properly in the LDIF file prior to parsing. It is implemented via the AttributeValidationPolicy interface and the DefaultAttributeValidationPolicy class. These two are located in the org.springframework.ldap.ldif.support package. The DefaultAttributeValidationPolicy uses regular expressions to validate attribute format according to RFC 2849.
  • The record specification policy is used to validate rules that each LDIF record must confirm to. Spring LDAP provides the Specification interface and two implementations for this policy: org.springframework.ldap.schema.DefaultSchemaSpecification and org.springframework.ldap.schema.BasicSchemaSpecification. The DefaultSchemaSpecification has an empty implementation and does not really validate the records. The BasicSchemaSpecification can be used to perform basic checks such as that an objectClass must exist for each LAP entry. For most cases, the BasicSchemaSpecification will suffice.

The modified parse method implementation, along with the three policy definitions, is given in Listing 10-11.

Listing 10-11.

package com.inflinx.book.ldap.parser;
 
import java.io.File;
import java.io.IOException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.ldap.core.LdapAttributes;
import org.springframework.ldap.ldif.parser.LdifParser;
import org.springframework.ldap.ldif.support.DefaultAttributeValidationPolicy;
import org.springframework.ldap.schema.BasicSchemaSpecification;
 
public class SimpleLdifParser2 {
 
   public void parse(File file) throws IOException {
      LdifParser parser = new LdifParser(file);
       parser.setAttributeValidationPolicy(new DefaultAttributeValidationPolicy());
      parser.setRecordSpecification(new BasicSchemaSpecification());
      parser.open();
          int count = 0;
      while(parser.hasMoreRecords()) {
         LdapAttributes attributes = parser.getRecord();
         count ++;
      }
      parser.close();
      System.out.println(count);
   }
 
   public static void main(String[] args) throws IOException {
      SimpleLdifParser2 parser = new SimpleLdifParser2();
      parser.parse(new ClassPathResource("patrons.ldif").getFile());
   }
}

Upon running the above method, you should see the count 103 in the console.

LDAP Connection Pooling

LDAP connection pooling is a technique where connections to LDAP directory are reused rather than being created each time a connection is requested. Without connection pooling, each request to LDAP directory causes a new connection to be created and then released when the connection is no longer required. Creating a new connection is resource-intensive and this overhead can have adverse effects on performance. With connection pooling, connections are stored in pool after they are created and are recycled for subsequent client requests.

Connections in a pool at any point can be in one of these three states:

  • In Use: The connection is open and currently in use.
  • Idle: The connection is open and available for reuse.
  • Closed: The connection is no longer available for use.

Figure 10-1 illustrates the possible actions on a connection at any given time.

9781430263975_Fig10-01.jpg

Figure 10-1. Connection pool states

Built In Connection Pooling

JNDI provides basic support for connection pooling via the "com.sun.jndi.ldap.connect.pool" environment property. Applications creating a directory context can set this property to true and indicate that connection pooling needs to be turned on. Listing 10-12 shows the plain JNDI code that utilizes pooling support.

Listing 10-12.

// Set up environment for creating initial context
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:11389");
 
// Enable connection pooling
env.put("com.sun.jndi.ldap.connect.pool", "true");
 
// Create one initial context
(Get connection from pool) DirContext ctx = new InitialDirContext(env);
 
// do something useful with ctx
// Close the context when we’re done
ctx.close(); // Return connection to pool

By default the contexts created using Spring LDAP have the "com.sun.jndi.ldap.connect.pool" property set to false. The native connection pooling can be turned on by setting the pooled property of the LdapContextSource to true in the configuration file. The following code shows the configuration change:

<bean id="contextSource" class="org.springframework.ldap.core.support.LdapContextSource">
   <property name="url" value="ldap://localhost:11389" />
   <property name="base" value="dc=example,dc=com" />
   <property name="userDn" value="cn=Manager" />
   <property name="password" value="secret" />
   <property name="pooled" value="true"/>
</bean>

Though the native LDAP connection pooling is simple, it does suffer from certain drawbacks. The pool of connections is maintained per the Java Runtime Environment. It is not possible to maintain multiple connection pools per JVM. Also, there is no control over the properties of the connection pool, such as the number of connections to be maintained at any time or idle connection time. It is also not possible to provide any custom connection validation to ensure that pooled connections are still valid.

Spring LDAP Connection Pooling

In order to address shortcomings with native JNDI pooling, Spring LDAP provides a custom pooling library for LDAP connections. The Spring LDAP pooling library maintains its own set of LDAP connections that are specific to each application.

image Note   Spring LDAP utilizes the Jakarta Commons Pool library for its underlying pooling implementation.

Central to Spring LDAP pooling is the org.springframework.ldap.pool.factory.PoolingContextSource, which is a specialized ContextSource implementation and is responsible for pooling DirContext instances. To utilize connection pooling, you start by configuring a Spring LDAP Context Source, as shown:

<bean id="contextSourceTarget" 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" />
   <property name="pooled" value="false"/>
</bean>

Note that you have the pooled property of the context source set to false. This will allow the LdapContextSource create brand new connections when the need arises. Also, the id of the ContextSource is now set to contextSourceTarget instead of contextSource, which is what you usually use. The next step is to create a PoolingContextSource, as shown:

<bean id="contextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
   <property name="contextSource" ref="contextSourceTarget" />
</bean>

The PoolingContextSource wraps the contextSourceTarget you configured earlier. This is required since the PoolingContextSource delegates the actual creation of DirContexts to the contextSourceTarget. Also note that you have used the id contextSource for this bean instance. This allows you to keep the configuration changes to minimum while using a PoolingContextSource instance in an LdapTemplate, as shown:

<bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
   <constructor-arg ref="contextSource" />
</bean>

The PoolingContextSource provides a variety of options that can be used to fine-tune connection pooling. Table 10-1 lists some of the important configuration properties.

Table 10-1. PoolingContextSource Configuration Properties

Property Description Default
testOnBorrow When set to true, the DirContext is validated before it is borrowed from the pool. If the DirContext fails validation, it is removed from the pool and a new attempt is made to borrow another DirContext. This testing might add a small delay in serving the borrow request. False
testOnReturn When set to true, this property indicates that DirContext will be validated before returning to the pool. False
testWhileIdle When set to true, this property indicates that idle DirContext instances in the pool should be validated at a specified frequency. Objects failing the validation will be dropped from the pool. False
timeBetweenEvictionRunsMillis This property indicates the time in milliseconds to sleep between running idle context tests. A negative number indicates that idle test will never be run. -1
whenExhaustedAction Specifies the action to be taken when the pool is exhausted. The possible options are WHEN_EXHAUSTED_FAIL (0), WHEN_EXHAUSTED_BLOCK (1), and WHEN_EXHAUSTED_GROW (2). 1
maxTotal The maximum number of active connections that this pool can contain. A non-positive integer indicates no limit. -1
maxIdle The maximum number of idle connections of each type (read, read-write) that can be idle in the pool. 8
maxWait The maximum number of milliseconds that a pool will wait for a connection to be returned to the pool before throwing an exception. A negative number indicates indefinite wait. -1

Pool Validation

Spring LDAP makes it easy to validate pooled connections. This validation ensures that the DirContext instances are properly configured and connected to LDAP server before they are borrowed from the pool. The same validation is done before the contexts are returned to the pool or on the contexts sitting idle in the pool.

The PoolingContextSource delegates the actual validation to concrete instances of the org.springframework.ldap.pool.validation.DirContextValidator interface. In Listing 10-13 you can see that the DirContextValidator has only one method: validateDirContext. The first parameter, contextType, indicates if the context to be validated is a read-only or a read-write context. The second parameter is the actual context that needs to be validated.

Listing 10-13.

package org.springframework.ldap.pool.validation;
 
import javax.naming.directory.DirContext;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.pool.DirContextType;
 
public interface DirContextValidator {
   boolean validateDirContext(DirContextType contextType, DirContext dirContext);
}

Out of the box, Spring LDAP provides an aptly named default implementation of the DirContextValidator called org.springframework.ldap.pool.validation.DefaultDirContextValidator. This implementation simply performs a search using the context and verifies the returned javax.naming.NamingEnumeration. If the NamingEnumeration does not contain any results or if an exception is thrown, the context fails the validation and will be removed from the pool. Applications requiring more sophisticated validation can create new implementations of the DirContextValidator interface.

Configuring pooling validation is shown in Listing 10-14. You start by creating a dirContextValidator bean of type DefaultDirContextValidator. Then you modify the contextSource bean declaration to include the dirContextValidator bean. In Listing 10-14, you have also added the testOnBorrow and testWhileIdle properties.

Listing 10-14.

<bean id="dirContextValidator" class="org.springframework.ldap.pool.validation.DefaultDirContextValidator" />
<bean id="contextSource" class="org.springframework.ldap.pool.factory.PoolingContextSource">
   <property name="contextSource" ref="contextSourceTarget" />
   <property name="dirContextValidator" ref="dirContextValidator"/>
   <property name="testOnBorrow" value="true" />
   <property name="testWhileIdle" value="true" />
</bean>

Summary

This brings us to the end of our journey. Throughout the book, you have learned the key features of Spring LDAP. With this knowledge, you should be ready to start developing Spring LDAP-based applications.

Finally, it has been an absolute pleasure writing this book and sharing my insights with you. I wish you all the best. Happy coding!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset