65.9K
CodeProject 正在变化。 阅读更多。
Home

Java: 使用 COM4J 从 Active Directory 获取用户的信息,例如电子邮件和职位

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2013 年 4 月 5 日

CPOL

2分钟阅读

viewsIcon

48715

downloadIcon

501

本文展示了 Java 应用程序如何从 Active Directory 检索用户数据。

引言

本文档简要介绍了如何使用 COM4J 从 Active Directory 获取“扩展”的用户数据。

动机

我在实施 SSO(单点登录)项目时遇到了这个问题。

使用 Java 实现基于 AD 的 SSO 比使用“原生”语言(如 C++)要复杂得多。即使 C#,虽然不被认为是“原生”语言,也提供了更好的机会。幸运的是,像 Waffle 这样的项目使生活变得更加轻松。然而,即使 Waffle 在很多方面都有帮助,它也有其局限性。例如,它无法从 AD 检索所有所需参数。Waffle 所做的是,它实现了本地机器的 Windows 和 Active Directory 之间的协商,从而执行 SSO 机制。用户身份验证完成后,如果您想从 AD 检索有关此用户的更多详细信息怎么办?例如,如果您想从 AD 获取用户的电子邮件、电话、地址等 - 使用 Waffle,这是不可能的。

再次幸运的是,有一些由 Java 包装的本机工具,例如 COM4J。

在下面的代码中,我将展示如何使用 COM4J 实现此目的。

代码

如上所述,可以通过本机调用检索来自 AD 的附加信息。COM4J 是一个不错的选择。如果您想使用 COM4J,您需要将几个 JAR 文件添加到您的依赖项中:ado20-1.0.jaractive-directory-1.0.jarcom4j-20110320.jar。如果您足够聪明并且使用 Maven,请将以下内容添加到您的 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>

代码的主要部分,也是最有趣的部分,是类 ActiveDirectoryUserInfo 的构造函数。在这里,我们访问 AD 并获取相关数据。请注意,构造函数是 private ,因为它是一个单例。

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();
}

构建 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());
         }
     }
} 

初始化命名上下文

/**
* "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

要使用此类,客户端应用程序必须提供两件事:用户在域中的完全限定名称,以及包含 AD 中所有感兴趣字段的 string (逗号分隔)。这些 params 传递给“getInstance”方法,从而创建一个实例。然后,所有需要做的是调用 map 的 getter,并获取所需的信息。

在下面的示例中,我们对用户的电子邮件、电话和其他参数感兴趣。用户的 FQN 是“john\doe”,这意味着域名是“john”,用户名是“doe”。

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");

请注意,不应像本示例中描述的那样对任何内容进行硬编码;这仅用于演示目的。

贷方

感谢 Christophe Dupriez 的大力支持,他在我努力解决这个挑战时给予了我帮助和指导。

© . All rights reserved.