我有一个简单的任务,即使用Java对Active Directory进行身份验证。只是验证凭据而不是其他任何内容。假设我的域名为“fun.xyz.tld”,OU路径未知,用户名/密码为testu / testp。
我知道有一些Java库可以简化这项任务,但我没有成功实现它们。我发现的大多数示例都是针对LDAP的,而不是特定的Active Directory。发出LDAP请求意味着在其中发送OU路径,这是我没有的。此外,发出LDAP请求的应用程序应该已绑定到Active Directory以便访问它...不安全,因为凭据必须存储在某处可被发现的位置。如果可能的话,我希望测试绑定测试凭据 - 这意味着帐户有效。
最后,如果可能的话,有没有办法让这种身份验证机制加密?我知道AD使用Kerberos,但不确定Java的LDAP方法是否可以。
有没有人有代码工作的例子?感谢。
答案 0 :(得分:94)
有3种身份验证协议可用于在Linux或任何其他平台上的Java和Active Directory之间执行身份验证(这些协议不仅仅针对HTTP服务):
Kerberos - Kerberos提供单点登录(SSO)和委派,但Web服务器也需要SPNEGO支持才能通过IE接受SSO。
NTLM - NTLM通过IE(以及其他浏览器,如果配置正确)支持SSO。
LDAP - LDAP绑定可用于简单验证帐户名和密码。
还有一种称为“ADFS”的东西,它为使用调用Windows SSP的SAML的网站提供SSO,因此在实践中它基本上是使用上述其他协议之一的迂回方式。
每个协议都有它的优点但是根据经验,为了获得最大的兼容性,你通常应该尝试“像Windows一样”。那Windows做了什么?
首先,两台Windows机器之间的身份验证支持Kerberos,因为服务器不需要与DC通信,客户端可以缓存Kerberos票证,从而减少DC上的负载(并且因为Kerberos支持委派)。
但是,如果身份验证方都没有域帐户或客户端无法与DC通信,则需要NTLM。因此,Kerberos和NTLM不是互斥的,并且Kerberos不会废弃NTLM。事实上,在某些方面,NTLM比Kerberos更好。请注意,当同时提到Kerberos和NTLM时,我还要提到SPENGO和集成Windows身份验证(IWA)。 IWA是一个简单的术语,基本上意味着Kerberos或NTLM或SPNEGO协商Kerberos或NTLM。
使用LDAP绑定作为验证凭据的方法效率不高,需要SSL。但直到最近,实施Kerberos和NTLM一直很困难,因此使用LDAP作为生成班次认证服务仍然存在。但在这一点上通常应该避免。 LDAP是信息目录,而不是身份验证服务。将它用于预期目的。
那么如何在Java中以及在Web应用程序的上下文中实现Kerberos或NTLM?
有许多像Quest Software和Centrify这样的大公司都有专门提到Java的解决方案。我无法对这些进行评论,因为它们是公司范围内的“身份管理解决方案”,所以,通过查看其网站上的营销活动,很难确切地说明正在使用哪些协议以及如何使用。您需要与他们联系以获取详细信息。
在Java中实现Kerberos并不是非常困难,因为标准Java库通过org.ietf.gssapi类支持Kerberos。然而,直到最近还存在一个主要障碍 - IE不发送原始Kerberos令牌,它发送SPNEGO令牌。但是使用Java 6,SPNEGO已经实现。从理论上讲,您应该能够编写一些可以验证IE客户端的GSSAPI代码。但我没试过。多年来,Sun实施Kerberos一直是一个错误的喜剧,所以根据Sun在这个领域的记录,我不会对他们的SPENGO实施作出任何承诺,直到你掌握了这只鸟。
对于NTLM,有一个名为JCIFS的免费OSS项目,它具有NTLM HTTP身份验证Servlet过滤器。但是,它使用中间人方法来验证使用不能与NTLMv2一起使用的SMB服务器的凭证(这正逐渐成为必需的域安全策略)。出于这个原因和其他原因,计划删除JCIFS的HTTP过滤器部分。请注意,有许多衍生产品使用JCIFS来实现相同的技术。因此,如果您看到其他声称支持NTLM SSO的项目,请查看小字。
使用Active Directory验证NTLM凭据的唯一正确方法是使用带有安全通道的NETLOGON上的NetrLogonSamLogon DCERPC调用。 Java中是否存在这样的事情?是。这是:
http://www.ioplex.com/jespa.html
Jespa是一个100%的Java NTLM实现,支持NTLMv2,NTLMv1,完整的完整性和机密性选项以及前面提到的NETLOGON凭证验证。它包括HTTP SSO过滤器,JAAS LoginModule,HTTP客户端,SASL客户端和服务器(带有JNDI绑定),用于创建自定义NTLM服务的通用“安全提供程序”等。
麦克
答案 1 :(得分:49)
以下是我根据此博客中的示例汇总的代码:LINK和此来源:LINK。
import com.sun.jndi.ldap.LdapCtxFactory;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Iterator;
import javax.naming.Context;
import javax.naming.AuthenticationException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import static javax.naming.directory.SearchControls.SUBTREE_SCOPE;
class App2 {
public static void main(String[] args) {
if (args.length != 4 && args.length != 2) {
System.out.println("Purpose: authenticate user against Active Directory and list group membership.");
System.out.println("Usage: App2 <username> <password> <domain> <server>");
System.out.println("Short usage: App2 <username> <password>");
System.out.println("(short usage assumes 'xyz.tld' as domain and 'abc' as server)");
System.exit(1);
}
String domainName;
String serverName;
if (args.length == 4) {
domainName = args[2];
serverName = args[3];
} else {
domainName = "xyz.tld";
serverName = "abc";
}
String username = args[0];
String password = args[1];
System.out
.println("Authenticating " + username + "@" + domainName + " through " + serverName + "." + domainName);
// bind by using the specified username/password
Hashtable props = new Hashtable();
String principalName = username + "@" + domainName;
props.put(Context.SECURITY_PRINCIPAL, principalName);
props.put(Context.SECURITY_CREDENTIALS, password);
DirContext context;
try {
context = LdapCtxFactory.getLdapCtxInstance("ldap://" + serverName + "." + domainName + '/', props);
System.out.println("Authentication succeeded!");
// locate this user's record
SearchControls controls = new SearchControls();
controls.setSearchScope(SUBTREE_SCOPE);
NamingEnumeration<SearchResult> renum = context.search(toDC(domainName),
"(& (userPrincipalName=" + principalName + ")(objectClass=user))", controls);
if (!renum.hasMore()) {
System.out.println("Cannot locate user information for " + username);
System.exit(1);
}
SearchResult result = renum.next();
List<String> groups = new ArrayList<String>();
Attribute memberOf = result.getAttributes().get("memberOf");
if (memberOf != null) {// null if this user belongs to no group at all
for (int i = 0; i < memberOf.size(); i++) {
Attributes atts = context.getAttributes(memberOf.get(i).toString(), new String[] { "CN" });
Attribute att = atts.get("CN");
groups.add(att.get().toString());
}
}
context.close();
System.out.println();
System.out.println("User belongs to: ");
Iterator ig = groups.iterator();
while (ig.hasNext()) {
System.out.println(" " + ig.next());
}
} catch (AuthenticationException a) {
System.out.println("Authentication failed: " + a);
System.exit(1);
} catch (NamingException e) {
System.out.println("Failed to bind to LDAP / get account information: " + e);
System.exit(1);
}
}
private static String toDC(String domainName) {
StringBuilder buf = new StringBuilder();
for (String token : domainName.split("\\.")) {
if (token.length() == 0)
continue; // defensive check
if (buf.length() > 0)
buf.append(",");
buf.append("DC=").append(token);
}
return buf.toString();
}
}
答案 2 :(得分:6)
我刚刚完成了一个使用AD和Java的项目。 我们使用了Spring ldapTemplate。
AD符合LDAP标准(差不多),我认为您的任务不会有任何问题。我的意思是它是AD或任何其他LDAP服务器,如果你只想连接它并不重要。
我会看一下:Spring LDAP
他们也有例子。
至于加密,我们使用SSL连接(因此它是LDAPS)。必须在SSL端口/协议上配置AD。
但首先,请确保您可以通过LDAP IDE正确连接到AD。我使用Apache Directory Studio,它非常酷,它是用Java编写的。这就是我所需要的一切。出于测试目的,您还可以安装Apache Directory Server
答案 3 :(得分:5)
正如ioplex和其他人所说,有很多选择。要使用LDAP(和Novell LDAP API)进行身份验证,我使用了类似的东西:
LDAPConnection connection = new LDAPConnection( new LDAPJSSEStartTLSFactory() );
connection.connect(hostname, port);
connection.startTLS();
connection.bind(LDAPConnection.LDAP_V3, username+"@"+domain, password.getBytes());
作为“特殊功能”,Active Directory允许LDAP绑定“user @ domain”而不使用帐户的可分辨名称。此代码使用StartTLS在连接上启用TLS加密;另一种选择是LDAP over SSL,我的 AD服务器不支持。
真正的诀窍是找到服务器和主机;官方的方法是使用DNS SRV(服务)记录查找来定位一组候选主机,然后执行基于UDP的LDAP“ping”(以特定的Microsoft格式)来定位正确的服务器。如果您有兴趣,我已经发布了一些blog articles关于我在该地区的冒险和探索之旅的信息。
如果你想进行基于Kerberos的用户名/密码验证,你会看到另一条鱼;它可以使用Java GSS-API代码,但我不确定它是否执行验证身份验证的最后一步。 (执行验证的代码可以联系AD服务器以检查用户名和密码,这会导致为用户授予票证,但为了确保AD服务器没有被模拟,它还需要尝试获取票证用户自己,这有点复杂。)
如果要进行基于Kerberos的单点登录,假设您的用户已对域进行身份验证,那么您也可以使用Java GSS-API代码执行此操作。我会发布一个代码示例,但我仍然需要将我可怕的原型变成适合人眼的东西。查看some code from SpringSource获取一些灵感。
如果你正在寻找NTLM(我被理解为不太安全)或其他东西,那么,祝你好运。
答案 4 :(得分:3)
您只是验证凭据吗?在这种情况下,你可以只做kerberos
而不用LDAP
。
答案 5 :(得分:2)
如果你想要做的就是使用Kerberos对AD进行身份验证,那么一个简单的http://spnego.sourceforge.net/HelloKDC.java程序应该这样做。
看看该项目的“飞行前”文档,该文档讨论了HelloKDC.java程序。
答案 6 :(得分:1)
http://java.sun.com/docs/books/tutorial/jndi/ldap/auth_mechs.html
SASL机制支持Kerberos v4和v5。 http://java.sun.com/docs/books/tutorial/jndi/ldap/sasl.html
答案 7 :(得分:1)
没有SSL的ldap身份验证不安全,任何人都可以查看用户凭据,因为ldap客户端在ldap绑定操作期间传输usernamae和密码所以始终使用ldaps协议。 来源:Ldap authentication Active directory in Java Spring Security with Example
答案 8 :(得分:0)