Using Java and Spring to connect to an Active Directory

You might find yourself in a position where you want to add, edit or delete accounts in your Active Directory. The OSB itself doesn’t have an adapter for it so how do you resolve this?

One of the options is to write a custom java library, add it to your domain and invoke it using a java-callout. Let’s start by making a simple Java project making use of Maven and Spring to connect to a Active Directory. Spring has a LDAP component which make it easy to connect to directories. Our pom file will look like this:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>nl.redrock</groupId>
    <artifactId>ActiveDirectoryAdapter</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>
    <name>Java Active Directory adapter</name>
 
    <properties>
        <spring.version>3.0.2.RELEASE</spring.version>
        <java.version>1.6</java.version>
        <ssl.trustStore>jssecacerts</ssl.trustStore>
    </properties>
 
    <build>
        <plugins>
            <!-- plugin for setting the compiler version -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <!-- Plugin for setting properties for the tests to run -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <systemProperties>
                        <property>
                            <name>javax.net.ssl.trustStore</name>
                            <value>${ssl.trustStore}</value>
                        </property>
                    </systemProperties>
                </configuration>
            </plugin>
            <!-- maven jar plugin to package all classes needed -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/lib</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <!-- Plugin to make the ActiveDirectoryService the main class in the jar -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <!-- <classpathPrefix>lib/</classpathPrefix> -->
                            <mainClass>nl.roceindhoven.service.ActiveDirectoryService</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>
 
    <reporting>
        <plugins>
            <!-- PMD -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-pmd-plugin</artifactId>
                <version>2.5</version>
                <configuration>
                    <linkXref>true</linkXref>
                    <sourceEncoding>utf-8</sourceEncoding>
                    <minimumTokens>100</minimumTokens>
                    <targetJdk>${java.version}</targetJdk>
                </configuration>
            </plugin>
            <!-- Checkstyle -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-checkstyle-plugin</artifactId>
                <version>2.7</version>
            </plugin>
            <!-- Findbugs -->
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>findbugs-maven-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <findbugsXmlOutput>true</findbugsXmlOutput>
                    <findbugsXmlOutputDirectory>target/site</findbugsXmlOutputDirectory>
                </configuration>
            </plugin>
            <!-- Plugin for cross references -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jxr-plugin</artifactId>
                <version>2.3</version>
            </plugin>
        </plugins>
    </reporting>
 
    <dependencies>
        <!-- Spring Dependencies -->
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-core-tiger</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-odm</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-ldif-core</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.ldap</groupId>
            <artifactId>spring-ldap-ldif-batch</artifactId>
            <version>1.3.1.RELEASE</version>
        </dependency>
        <!-- Test dependencies -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.7</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
 
    <!-- Add the Spring Maven Repositories to this project -->
    <repositories>
        <repository>
            <id>spring-release</id>
            <url>http://maven.springframework.org/release</url>
        </repository>
    </repositories>
</project>

With this setup, all the required libraries are included to connect to your AD. Next up is our applicationContext.xml. In here we define our beans which we can use to access the AD. This looks like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <bean id="contextSource"
          class="org.springframework.ldap.core.support.LdapContextSource">
        <!-- <property name="url" value="ldap://YOUR_SERVER:389"/> -->
        <property name="url" value="ldaps://YOUR_SERVER:636"/>
        <property name="base" value="OU=PRD,DC=redrock,DC=nl"/>
        <property name="userDn" value="CN=admin,OU=PRD,DC=redrock,DC=nl"/>
        <property name="password" value="MYPASSWORD"/>
        <property name="referral" value="follow"/>
        <property name="baseEnvironmentProperties">
            <map>
                <entry key="java.naming.security.authentication" value="simple"/>
            </map>
        </property>
    </bean>
     
    <bean id="ldapTemplate" class="org.springframework.ldap.core.LdapTemplate">
        <constructor-arg ref="contextSource"/>
    </bean>
     
    <bean id="ldapUserDAOBean" class="nl.redrock.dao.impl.UserDaoSpringImpl">
        <property name="ldapTemplate" ref="ldapTemplate"/>
    </bean>
     
</beans>

There are a few things to watch for. For example any spaces in the BASE or USERDN variable. Make sure you remove them so it looks like this: “OU=prd,DC=redrock,DC=nl” instead of this: “OU=prd, DC=redrock, DC=nl”. This might cause problems. Also remember that you can only set passwords for a user when you connect to the directory using HTTPS. This only changes the connection string from ldap://YOUR_SERVER:389 to ldaps://YOUR_SERVER:636. These are default ports btw.

Now we can create the UserDaoSpringImpl class and it’s UserDao interface. The UserDao can be quite simple. Let’s start with just looking up people in the Active Directory. The UserDao looks like this then:

package nl.redrock.dao;
 
import java.util.List;
 
import nl.redrock.domain.User;
import nl.redrock.domain.UserDetails;
 
/**
 * The User Data Access Object interface which can be used to manipulate users
 * in the active directory
 *
 * @author Hugo Hendriks
 *
 */
public interface UserDao {
 
    /**
     * retrieve all the users from the active directory
     *
     * @return a list of Users
     */
    public List getAllUsers();
 
}

An implementation of this would look like this:

package nl.redrock.dao.impl;
 
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
 
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
 
import nl.redrock.dao.UserDao;
import nl.redrock.domain.User;
import nl.redrock.domain.UserDetails;
import nl.redrock.exception.DuplicateUserException;
import nl.redrock.exception.LdapException;
import nl.redrock.exception.PasswordStrengthException;
import nl.redrock.utils.ActiveDirectoryUtils;
import nl.redrock.utils.UserContextMapper;
 
import org.springframework.ldap.NameAlreadyBoundException;
import org.springframework.ldap.OperationNotSupportedException;
import org.springframework.ldap.core.DirContextAdapter;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.core.support.AbstractContextMapper;
import org.springframework.ldap.filter.AndFilter;
import org.springframework.ldap.filter.EqualsFilter;
import org.springframework.ldap.filter.Filter;
 
/**
 * The implementation of the User Data Access Object.
 *
 * @author Hugo
 *
 */
public class UserDaoSpringImpl implements UserDao {
 
    // Attribute names
    private static final String USER_ACCOUNT_CONTROL_ATTR_NAME = "userAccountControl";
    private static final String ACCOUNT_EXPIRES_ATTR_NAME = "accountExpires";
    private static final String PWD_LAST_SET_ATTR_NAME = "pwdLastSet";
    private static final String PASSWORD_ATTR_NAME = "unicodepwd";
    private static final String DISTINGUISHED_NAME_ATTR_NAME = "distinguishedname";
    private static final String MEMBER_ATTR_NAME = "member";
 
    // usercontrol params
    private static final int FLAG_TO_DISABLE_USER = 0x2;
    private static final int ADS_UF_DONT_EXPIRE_PASSWD = 0x10000;
    private static final int USER_CONTROL_NORMAL_USER = 512;
 
    private LdapTemplate ldapTemplate;
    ActiveDirectoryUtils activeDirectoryUtils = new ActiveDirectoryUtils();
 
    /**
     * {@inheritDoc}
     */
    @SuppressWarnings("unchecked")
    public List getAllUsers() {
        SearchControls controls = new SearchControls();
        controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        return ldapTemplate.search("", "(objectclass=person)", controls,
                new UserContextMapper());
    }
 
    public LdapTemplate getLdapTemplate() {
        return ldapTemplate;
    }
 
    public void setLdapTemplate(LdapTemplate ldapTemplate) {
        this.ldapTemplate = ldapTemplate;
    }
 
}

As you can see from the import I have more methods on the interface but I’m leaving them behind here due too the large nature of code then.

You can then write a class which creates the UserDao and exposes its methods….something like this:

package nl.redrock.service;
 
import java.util.List;
 
import nl.redrock.dao.UserDao;
import nl.redrock.dao.impl.UserDaoContextImpl;
import nl.redrock.dao.impl.UserDaoSpringImpl;
import nl.redrock.domain.User;
import nl.redrock.domain.UserDetails;
 
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
 
public class ActiveDirectoryService {
 
    /**
     * Return all users
     *
     * @return
     */
    public static List getAllUsers() {
        return createDao().getAllUsers();
    }
 
    /**
     * Create the userDao
     *
     * @return UserDao
     */
    private static UserDao createDao() {
        Resource resource = new ClassPathResource("/applicationContext.xml");
        BeanFactory factory = new XmlBeanFactory(resource);
        return(UserDaoSpringImpl) factory.getBean("ldapUserDAOBean");
    }
 
}

The last thing to do now to see if it works is to write a test.

package nl.redrock.service;
 
import java.util.Iterator;
import java.util.List;
 
import junit.framework.TestCase;
import nl.redrock.domain.User;
import nl.redrock.domain.UserDetails;
 
public class ActiveDirectorySpringServiceTest extends TestCase {
 
    public void testGetAllUsers() {
        List result = ActiveDirectoryService.getAllUsers();
        assertTrue(!result.isEmpty());
        for (Iterator iterator = result.iterator(); iterator.hasNext();) {
            User user = (User) iterator.next();
            System.out.println(user.toString());
        }
    }
}

Run the test and you should be able to see all your user in the Active Directory from the base you have set. If you get an PKIX exception when running, it means you have a problem connecting over SSL to the AD. Your truststore is propably not set yet with the right certificate. You can use the InstallCert.java class to connect to the AD and generate a jssecacerts file. Put this file the root of you project and you should be fine running the test with maven as you have configured in the pom that it uses this JKS to run the tests.

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <systemProperties>
            <property>
                <name>javax.net.ssl.trustStore</name>
                <value>${ssl.trustStore}</value>
            </property>
        </systemProperties>
    </configuration>
</plugin>

If you want to run in Eclipse for example you can set the VM arguments option on the Run Configuration:

3 Replies to “Using Java and Spring to connect to an Active Directory”

  1. Pingback: Using Groovy to connect to Active Directory « Hugo Hendriks @ RUBIX.nl

  2. I really would like to book mark this post, “Using Java and Spring to connect
    to an Active Directory « Hugo Hendriks @ RUBIX.nl” on my personal
    web-site. Do you really care in the event Ido it? Thanks a lot ,Marcela

Leave a Reply

Your email address will not be published. Required fields are marked *

*