在实现单点登录(SSO)项目的过程中,需要处理与Active Directory相关的用户数据。使用Java实现SSO比使用C++等“原生”语言要复杂得多。尽管C#也不是“原生”语言,但它提供了更好的机会。幸运的是,像Waffle这样的项目让生活变得更轻松。然而,即使Waffle在许多方面都有所帮助,它也有局限性。例如,它无法从AD检索所有所需的参数。Waffle所做的是实现本地计算机与活动目录之间的协商,从而执行SSO机制。一旦用户通过身份验证,如果想从AD检索有关此用户的更多详细信息,例如用户的电子邮件、电话、地址等,使用Waffle就是不可能的。
幸运的是,有一些Java包装的原生工具,如COM4J。在下面的代码中,将展示如何使用COM4J来实现这一目的。
如上所述,可以通过原生调用从AD检索额外的信息。COM4J是一个不错的选择。如果想使用COM4J,需要将一些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 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", "", "", -1);
// 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(";(" + searchField + "=" + username + ");" + requestedInfo + ";subTree");
_Recordset rs = cmd.execute(null, Variant.getMissing(), -1);
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();
}
构建信息映射:
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());
}
}
}
初始化命名上下文:
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);
}
}
要使用这个类,客户端应用程序需要提供两件事:域中用户的完全限定名,以及它感兴趣的AD中所有字段的字符串(用逗号分隔)。这些参数传递给"getInstance"方法,从而创建一个实例。然后,所需要做的就是调用映射的getter,并获取所需的信息。
在下面的例子中,对用户的电子邮件、电话号码和其他参数感兴趣。用户的FQN是"john\doe",意味着域名是"john",用户名是"doe"。
String requestedFields = "distinguishedName,userPrincipalName,telephoneNumber,mail";
// the fully qualified name of the user in the AD. \
String fqn = "john\\doe";
ActiveDirectoryUserInfo userInfo = ActiveDirectoryUserInfo.getInstance(fqn, requestedFields);
Map infoMap = userInfo.getInfoMap();
String email = infoMap.get("mail");