Click here to Skip to main content
Click here to Skip to main content

Java: Retrieving User Information, Such As Email Addresses, from Active Directory

, 15 Sep 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
This document describes how to get extended user’s data, such as email address, from the Active Directory. A former article describes how to do so using native tools, such as COM4J; however these tools are cumbersome for use, while pure Java is better and simple.

Introduction

This document describes how to get extended user data, such as an email address, from Active Directory (AD). A previous article described how to do so using native tools, such as COM4J. These tools, however, can be cumbersome to use, while using pure Java is much simpler and, hence, better: after all, what can be better and easier than a few simple calls to built-in Java packages?

Motivation

I had to deal with this issue while implementing an SSO (single sign on) project.

Implementing SSO over an AD is much more straight-forward in "native" languages such as C++ or even C#. In Java, however, things are more challenging and it took me a while to find this solution, which doesn’t need any native tools such as COM4J, or JNI calls to another C/C# program.

Projects such as Waffle make life much easier. It implements the negotiation between Windows, on the local machine, and the Active Directory, thus performing the SSO mechanism. However, even Waffle – which helps in many ways – has its limitations. For example, even though it does take care of the authentication, it cannot retrieve all the desired parameters from the AD. Using Waffle to retrieve the user's email address, telephone number, address, etc., is not possible.

To overcome this issue, one option is to use native tools, like COM4J. COM4J indeed works great, but its drawback is that it requires additional understanding and has its pitfalls. If everything works properly everyone is happy, but once problems arise, one has to dig in and resolve problems in corners no one ever really wanted to enter. For example, using COM4J forces the developer to include the relevant JARs in the build-path, or to worry about the COM4J.DLL version which is installed in the “web-inf/lib” directory (32/64? AMD?), etc.

This document shows how to use Java for this purpose, without the need of any other native tools, or any other dependencies. As an aside, I will mention that once everything is working, and you want to improve performance a bit, you can use Spring for LDAP, but let us leave this to the end.

The Code

I divided the code into three parts. The first part connects to the AD. The second uses the connection details, such as Context and SearchBase, and brings the data we want from the AD. The last part – well, this is the code that uses the first two parts to show how it works. I use Spring 3 to declare our beans as “Components”, and to autowire these beans to the class that uses them. A full discussion of this is out of scope here, and I assume the reader knows how to use Spring.

ActiveDirectoryConnectionUtils

The ActiveDirectoryConnectionUtils takes care of the connection. Explaining how to work with Java’s connection pool is out of scope of this document; for more about LDAP connection pooling, read Oracle’s “LDAP Connections” section.

@Component
public class ActiveDirectoryConnectionUtils
{
    public LdapContext createContext(String url, String user, String pass)
    {    	        Hashtable<String,String> env = getProperties(url, user, pass);
        LdapContext ctx;
        try
        {
	        ctx = new InitialLdapContext(env, null);
        }
        catch (NamingException e)
        {
	        throw new RuntimeException(e);
        }
        return ctx;
    }
    private Hashtable<String,String> getProperties(String serverUrl, String user, String password)
    {
        //create an initial directory context
        Hashtable<String,String> env = new Hashtable<String,String>();
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.REFERRAL, "ignore");
        env.put("com.sun.jndi.ldap.connect.pool", "false");
        //environment property to specify how long to wait for a pooled connection.
        // If you omit this property, the application will wait indefinitely.
        env.put("com.sun.jndi.ldap.connect.timeout", "300000");
        env.put(Context.PROVIDER_URL, serverUrl);
        env.put(Context.SECURITY_PRINCIPAL, user);
        env.put(Context.SECURITY_CREDENTIALS, password);
        env.put("java.naming.ldap.attributes.binary", "tokenGroups objectSid objectGUID");
        return env;
    }
}

ActiveDirectoryLdapService

This code basically goes to the AD, using the inputs it gets from the previous class we have just seen. First, it fetches from the AD all the data and stores it in NamingEnumeration<SearchResult>, which is an enumeration of the search results by the filter. Then, it searches this list for a specific attribute. In the code below, this attribute is the user’s email, and we search it by the property “AD_ATTR_NAME_USER_EMAIL”. This implementation is just an example, of course, and can vary from one client to another.

To learn more about LDAP filters, read here.

@Component
public class ActiveDirectoryLdapService
{
	private static Logger logger = Logger.getLogger(ActiveDirectoryLdapService.class);
    //Attribute names
    private static final String AD_ATTR_NAME_TOKEN_GROUPS = "tokenGroups";
    private static final String AD_ATTR_NAME_OBJECT_CLASS = "objectClass";
    private static final String AD_ATTR_NAME_OBJECT_CATEGORY = "objectCategory";
    private static final String AD_ATTR_NAME_MEMBER = "member";
    private static final String AD_ATTR_NAME_MEMBER_OF = "memberOf";
    private static final String AD_ATTR_NAME_DESCRIPTION = "description";
    private static final String AD_ATTR_NAME_OBJECT_GUID = "objectGUID";
    private static final String AD_ATTR_NAME_OBJECT_SID = "objectSid";
    private static final String AD_ATTR_NAME_DISTINGUISHED_NAME = "distinguishedName";
    private static final String AD_ATTR_NAME_CN = "cn";
    private static final String AD_ATTR_NAME_USER_PRINCIPAL_NAME = "userPrincipalName";
    private static final String AD_ATTR_NAME_USER_EMAIL = "mail";
    private static final String AD_ATTR_NAME_GROUP_TYPE = "groupType";
    private static final String AD_ATTR_NAME_SAM_ACCOUNT_TYPE = "sAMAccountType";
    private static final String AD_ATTR_NAME_USER_ACCOUNT_CONTROL = "userAccountControl";
    
	/**
	 * 
	 * @param ctx
	 * @param searchBase
	 * @param domainWithUser: suck as "MYDOMAIN\myUser"
	 * @return
	 */
    public String getUserMailByDomainWithUser(LdapContext ctx, String searchBase, String domainWithUser) 
	{
		logger.debug("trying to get email of domainWithUser " + 
		domainWithUser + " using baseDN " + searchBase);
		String userName = domainWithUser.substring(domainWithUser.indexOf('\\') +1 );
		try
		{
	    	NamingEnumeration<SearchResult> 
	    	userDataBysAMAccountName = getUserDataBysAMAccountName(ctx, searchBase, userName);
	    	return getUserMailFromSearchResults( userDataBysAMAccountName );
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}
		private NamingEnumeration<SearchResult> 
		getUserDataBysAMAccountName(LdapContext ctx, String searchBase, String username) 
			throws Exception 
	{
		String filter = "(&(&(objectClass=person)
		(objectCategory=user))(sAMAccountName=" + username + "))";
        SearchControls searchCtls = new SearchControls();
        searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        NamingEnumeration<SearchResult> answer = null;
        try
        {
	        answer = ctx.search(searchBase, filter, searchCtls);
        }
        catch (Exception e)
        {
        	logger.error("Error searching Active directory for " + filter);
        	throw e;
        }
        
        return answer;
	}        
    
	private String getUserMailFromSearchResults( NamingEnumeration<SearchResult> userData ) 
    		throws Exception 
	{
        try
        {
	        String mail = null;
		        // getting only the first result if we have more than one
	        if (userData.hasMoreElements())
	        {
	            SearchResult sr = userData.nextElement();
	            Attributes attributes = sr.getAttributes();
		            mail = attributes.get(AD_ATTR_NAME_USER_EMAIL).get().toString();
	            logger.debug("found email " + mail);
	        }
	        
	        return mail;
        }
        catch (Exception e)
        {
        	logger.error("Error fetching attribute from object");
        	throw e;
        }        
	}
}

Putting It All Together

To use the code above, the user has to call only two methods: createContext(), and then after getting the context, getUserMailByDomainWithUser().
The client-application has to supply the following:

  • The URL to the LDAP server
  • The credentials to this server (username and password)
  • A String which is the SearchBase path in the AD
  • The FQN (fully qualified name) of the user in the AD

In the example below, we are interested in the users’ email only. The first three parameters above are configured per system, hence they are read from a property file. For the purposes of this example, we can hard-code them. The only runtime changeable parameter is the FQN of the user, whose email we are looking for.
The FQN should look like "john\doe”, meaning the domain name is "john” and the user name is "doe”.

public class LdapTester
{
	@Value("${com.watchdox.kerberos.ad.url}")
	private String url;
		@Value("${com.watchdox.kerberos.ad.username}")
	private String username;
		@Value("${com.watchdox.kerberos.ad.password}")
	private String password;
		@Value("${com.watchdox.kerberos.ad.baseDN}")
	private String baseDN;
	@Autowired
	private ActiveDirectoryConnectionUtils adConnectionUtils;
		@Autowired
	private ActiveDirectoryLdapService adLdapService;
			public void testGetUserMailByDomainWithUser(String fqn)
	{
		LdapContext ctx = adConnectionUtils.createContext(url, username, password);
		String email = adLdapService.getUserMailByDomainWithUser(ctx, baseDN, fqn);
	}
}

Performance

The code above opens a connection for each access to AD, which can cause performance issues. This can be tackled in several ways. IMHO, the easiest one to implement is a connection-pool, which can be achieved using the Spring-LDAP package; the details of this are beyond the scope of this document.

Credits

My colleagues Shalom Kazaz and Or Gerson, and special thanks to Mr. David Goldhar.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Ohad Redlich

Israel Israel
worked for NICE systems (MFC C++ COM...)
worked for BMC software (Java, ...)
working for WatchDox (Java, Spring, Spring-Security...)
 
My Linkedin Profile
 
Visit my photography gallery

Comments and Discussions

 
QuestionPost the full source? Pinmemberrichardm001uk29-Apr-14 22:41 
GeneralMy vote of 5 PinmemberMember 1023332326-Sep-13 1:08 
GeneralMy vote of 5 Pinmemberzpassenger20-Sep-13 1:39 
GeneralMy vote of 5 Pinmemberzpassenger20-Sep-13 1:38 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141022.1 | Last Updated 15 Sep 2013
Article Copyright 2013 by Ohad Redlich
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid