Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Java

Java: Retrieving User's Information, Such As Email and Position, from Active Directory Using COM4J

5.00/5 (6 votes)
3 Jul 2013CPOL2 min read 47.7K   460  
This article shows how Java application can retrieve user's data from the Active Directory

Introduction

This short document describes the usage of COM4J in order to get "extended” user’s data from the Active Directory.

Motivation

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

Using Java, implementing SSO over an AD is much more cumbersome than in "native" languages such as C++. Even C#, which is not considered as a "native” language, offers better opportunities. Luckily, projects such as Waffle make life much easier. However though, even Waffle, that helps in many ways, has its limitations. For example, it cannot retrieve all desired parameters from the AD. What Waffle does do, it implements the negotiation between Windows of the local machine and the active directory, hence performs the SSO mechanism. Once the user is authenticated, what happens if you want to retrieve more details about this user from the AD? For example, if you want to get the user's email, telephone, address, etc. from the AD – using Waffle, it is simply impossible.

Lucky again, there are native tools that are wrapped by Java, such as COM4J.

In the code below, I will show how to use COM4J for such a purpose.

The Code

As the above, additional information from the AD can be retrieved by native calls. COM4J is a good option. If you want to use COM4J, you will have to add a few JARS to your dependencies: ado20-1.0.jar, active-directory-1.0.jar, and com4j-20110320.jar. If you are smart and work with Maven, add the following to your pom.xml:

XML
<dependency>
     <groupId>org.jvnet.com4j</groupId>
     <artifactId>com4j</artifactId>
     <version>20110320</version>
</dependency>
<dependency>
     <groupId>org.jvnet.com4j.typelibs</groupId>
     <artifactId>active-directory</artifactId>
     <version>1.0</version>
</dependency>
<dependency>
     <groupId>org.jvnet.com4j.typelibs</groupId>
     <artifactId>ado20</artifactId>
     <version>1.0</version>
</dependency>

The main part of code, and most interesting, is the constructor of the class ActiveDirectoryUserInfo. Here, we go to the AD and fetch the relevant data. Note that the constructor is private since it is singleton.

C#
private ActiveDirectoryUserInfo (String username, String requestedInfo) 
{
     infoMap.clear();
             
     initNamingContext();
     if (defaultNamingContext == null) {
             return;
     }
     //Searching LDAP requires ADO,so it's good to create a connection upfront for reuse. 
     _Connection con = ClassFactory.createConnection();
     con.provider("ADsDSOObject");
     con.open("Active Directory Provider",""/*default*/,""/*default*/,-1/*default*/);
     // query LDAP to find out the LDAP DN and other info for the given user from the login ID 
     _Command cmd = ClassFactory.createCommand();
     cmd.activeConnection(con);
     String searchField = "userPrincipalName";
     int pSlash = username.indexOf('\\');
     if (pSlash > 0) 
     {
             searchField = "sAMAccountName";
             username = username.substring(pSlash+1);
     }
     cmd.commandText("<LDAP://"+defaultNamingContext+">;("+searchField+"="+username+
                     ");"+requestedInfo+";subTree");
     _Recordset rs = cmd.execute(null, Variant.getMissing(), -1/*default*/);
  
             
     if(rs.eof()) 
     {
         // User not found!
         _log.error(username+" not found.");
     }
     else 
     {
         Fields userData = rs.fields();
         if (userData != null)
         {
              //see below: we build the map of requested-info:
              buildInfoMap(requestedInfo, userData);
         }
         else
         {
              _log.error("User "+username+" information is empty.");
         }
     }
             
     
     if(infoMap.isEmpty())
     {
         _log.error("user-info map is empty - no data was written to it.");
     }

     rs.close();
     con.close();
}

Building the InfoMap

Java
private void buildInfoMap(String requestedInfo, Fields userData) 
{
     StringTokenizer tokenizer = new StringTokenizer(requestedInfo, ",");
     String detail ;
     String value = null;
     while( tokenizer.hasMoreTokens() )
     { 
         detail = tokenizer.nextToken();
         try
         {
              Object o = userData.item(detail).value();
              if (o != null)
              {
                   value = o.toString();
                   _log.info(detail + " = " + value);
                  infoMap.put(detail, value);
              }
         }
         catch (ComException ecom ) 
         {
            _log.error(detail + " not returned: "+ecom.getMessage());
         }
     }
} 

Initializing the Naming Context

Java
/**
* "LDAP://RootDSE" connects the active directory that the local machine is connected to.
* if we want to support cases of multiple domains, and enable connection from one domain to
* another, we should pass the DSE as a param.
* @param domainServerAddress 
*/
synchronized void initNamingContext(String domainServerAddress) 
{
	_log.debug("* initNamingContext *, domainServerAddress= " + domainServerAddress);
	if (defaultNamingContext == null) 
	{
		if(domainServerAddress == null || domainServerAddress.isEmpty())
		{
			domainServerAddress = "RootDSE";
		}
		IADs rootDSE = COM4J.getObject(IADs.class, "LDAP://" + domainServerAddress, null);
		defaultNamingContext = (String)rootDSE.get("defaultNamingContext");
    	_log.info("defaultNamingContext= " + defaultNamingContext);
	}
} 

Using the Code

To use this class, the client-application has to supply two things: the fully-qualified-name of the user in the domain, and the string with all fields in the AD that it is interested in (comma separated). These params are passed to the "getInstance” method, thus creating an instance. Then, all needs to be done is a call to the getter of the map, and get the desired information.

In the example below, we are interested in the users’ email, telephone and other parameters. The FQN of the user is "john\doe”, meaning the domain name is "john” and the user name is "doe”.

Java
String requestedFields= "distinguishedName,userPrincipalName,telephoneNumber,mail";
//the fully qualified name of the user in the AD. <Domain-name>\<username>
String fqn = "john\doe"; 
ActiveDirectoryUserInfo userInfo = ActiveDirectoryUserInfo.getInstance(fqn, requestedFields);
Map<String, String> infoMap = userInfo.getInfoMap();
String email = infoMap.get("mail");

Note that nothing should be hard-coded as described in this example; this is only for demonstration purposes.

Credit

Big credit to Christophe Dupriez, who gave me a hand and a guiding light while I was struggling with this challenge.

License

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