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:
<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.
private ActiveDirectoryUserInfo (String username, String requestedInfo)
{
infoMap.clear();
initNamingContext();
if (defaultNamingContext == null) {
return;
}
_Connection con = ClassFactory.createConnection();
con.provider("ADsDSOObject");
con.open("Active Directory Provider","","",-1);
_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);
if(rs.eof())
{
_log.error(username+" not found.");
}
else
{
Fields userData = rs.fields();
if (userData != null)
{
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
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
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 param
s 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
”.
String requestedFields= "distinguishedName,userPrincipalName,telephoneNumber,mail";
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.