2008-12-23 151 views
63

我有一个使用Java对Active Directory进行身份验证的简单任务。只是验证凭证,没有别的。假设我的域名是“fun.xyz.tld”,OU路径是未知的,并且用户名/密码是testu/testp。在Linux上使用Java对Active Directory进行身份验证

我知道有一些Java库可以简化这个任务,但是我没有成功实现它们。我发现的大多数示例都是针对LDAP,而不是专门针对Active Directory。发出LDAP请求意味着发送一个OU路径,这是我没有的。此外,发出LDAP请求的应用程序应该已经绑定到Active Directory以便访问它...不安全,因为凭据必须存储在某个可发现的位置。如果可能的话,我想用测试凭证进行测试绑定 - 这意味着该帐户是有效的。

最后,如果可能的话,有没有办法使这样的认证机制加密?我知道AD使用Kerberos,但不确定Java的LDAP方法。

有没有人有工作代码的例子?谢谢。

回答

46

下面是我基于本博客示例编写的代码: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(); 
    } 

} 
+3

`import com.sun.jndi.ldap.LdapCtxFactory;` - 这很可能只适用于Sun JVM。 – 2010-07-21 14:25:06

3

你只是验证凭据吗?在这种情况下,你可以做简单的kerberos,而不要打扰LDAP

+0

是的,只验证凭证。我通过澄清编辑了这个问题。代码与LDAP身份验证有什么不同? – 2008-12-23 21:56:34

6

我刚刚完成了一个使用AD和Java的项目。 我们使用了Spring ldapTemplate。 AD是LDAP兼容(几乎),我不认为你会有任何问题,你有任务。我的意思是它是AD或任何其他LDAP服务器,如果你只是想连接它并不重要。

我会看看:Spring LDAP

他们有太多的例子。

至于加密,我们使用SSL连接(所以它是LDAPS)。 AD必须在SSL端口/协议上进行配置。

但首先,确保您可以通过LDAP IDE正确连接到您的AD。我使用Apache Directory Studio,它非常酷,它是用Java编写的。这就是我需要的。出于测试目的,您还可以安装Apache Directory Server

+0

Luchiani,我目前正在开发Web应用程序来集成java spring共享点(windows),而我无法使用java代码在活动目录中创建用户,您可以在活动目录中分享用于创建用户代码的评论这样我才能按时继续工作。 – 2014-11-21 13:42:35

90

有迹象表明,可用于Java和Linux或任何其它平台上的Active Directory(和这些不只是特定的HTTP服务)之间进行认证3个认证协议:

  1. 的Kerberos - Kerberos提供单点登录(SSO)和委托,但Web服务器也需要SPNEGO支持才能通过IE接受SSO。

  2. NTLM - NTLM通过IE(以及其他浏览器,如果它们配置正确)支持SSO。

  3. LDAP - LDAP绑定可用于简单验证帐户名称和密码。

还有一种叫“ADFS”,提供SSO使用SAML调用到Windows SSP网站,所以在实践中它基本上使用的是其它上述协议之一的环岛路。

每个协议都有它的优点,但作为一个经验法则,为了获得最大的兼容性,您通常应该尽量“像Windows一样做”。那么Windows做什么?

首先,两台Windows计算机之间的身份验证支持Kerberos,因为服务器不需要与DC通信,并且客户端可以缓存可减少DC负载(因为Kerberos支持委派)的Kerberos票据。

但是,如果认证方不都有域帐户,或者如果客户端无法与DC通信,则需要NTLM。所以Kerberos和NTLM并不相互排斥,NTLM也不会被Kerberos废弃。事实上,在某些方面,NTLM比Kerberos更好。请注意,当提到Kerberos和NTLM时,我不得不提到SPENGO和集成Windows身份验证(IWA)。 IWA是一个简单的术语,基本上意味着Kerberos或NTLM或SPNEGO来协商Kerberos或NTLM。

使用LDAP绑定作为验证凭据的方式效率不高,需要SSL。但直到最近,实施Kerberos和NTLM都很困难,所以使用LDAP作为转换认证服务一直存在。但在这一点上,通常应该避免。 LDAP是一个信息目录,而不是一个验证服务。用它来达到预期的目的。

那么您如何在Java中实现Kerberos或NTLM,特别是在Web应用程序的上下文中?

有许多像Quest Software和Centrify这样的大公司都有专门提到Java的解决方案。我不能评论这些,因为它们是公司范围内的“身份管理解决方案”,因此,从他们的网站上查看市场营销的角度来看,很难确切地说明正在使用什么协议以及如何使用。您需要联系他们了解详情。

在Java中实现Kerberos并不难,因为标准Java库通过org.ietf.gssapi类支持Kerberos。然而,直到最近还有一个主要障碍 - IE不发送原始的Kerberos令牌,它发送SPNEGO令牌。但是对于Java 6,SPNEGO已经实施。理论上你应该能够编写一些可以验证IE客户端的GSSAPI代码。但我没有尝试过。太阳实施的Kerberos多年来一直是一个错误喜剧,因此基于Sun在这方面的记录,我不会对他们的SPENGO实施做任何承诺,直到您掌握了这只鸟。

对于NTLM,有一个名为JCIFS的免费OSS项目,它具有NTLM HTTP身份验证Servlet筛选器。但是,它使用中间人方法来验证与不与NTLMv2配合使用的SMB服务器(它正在慢慢成为必需的域安全策略)来验证凭据。由于这个原因和其他原因,JCIFS的HTTP过滤器部分计划被删除。请注意,有多个使用JCIFS实施相同技术的分拆。因此,如果您看到其他宣称支持NTLM SSO的项目,请检查细则。

使用Active Directory验证NTLM凭据的唯一正确方法是通过NETLOGON与Secure Channel使用NetrLogonSamLogon DCERPC调用。 Java中存在这样的事情吗?是。那就是:

http://www.ioplex.com/jespa.html

Jespa是一个100%的Java NTLM实现支持NTLMv2的,NTLMv1身份,充分完整性和保密选项和上述NETLOGON凭据验证。它包括HTTP SSO筛选器,JAAS LoginModule,HTTP客户端,SASL客户端和服务器(具有JNDI绑定),用于创建定制NTLM服务的通用“安全提供程序”等等。

迈克

+3

更新:HttpClient现在支持基本,摘要,NTLMv1,NTLMv2,NTLM2会话,SNPNEGO,Kerberos身份验证方案。 – 2012-07-23 16:10:28

2

如果你想要做的使用Kerberos对广告进行身份验证,然后进行简单http://spnego.sourceforge.net/HelloKDC.java程序应该这样做。

看看项目的“pre-flight”文档,其中讨论了HelloKDC.java程序。

4

正如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结合对“用户@域”不使用的可分辨的名称帐户。此代码使用StartTLS在连接上启用TLS加密;另一种选择是通过SSL的LDAP,我的AD服务器不支持。

真正的诀窍在于定位服务器和主机;官方的方法是使用DNS SRV(服务)记录查找来定位一组候选主机,然后执行基于UDP的LDAP“ping”(以特定的Microsoft格式)以找到正确的服务器。如果你有兴趣,我已经发布了一些blog articles关于我在那个地区冒险和发现的旅程。

如果你想做基于Kerberos的用户名/密码认证,你正在看另一个水壶;它适用于Java GSS-API代码,但我不确定它是否执行验证身份验证的最后一步。 (执行验证的代码可以联系AD服务器来检查用户名和密码,这会为用户授予票证,但要确保AD服务器未被模拟,还需要尝试获取用户自己,这是更复杂一些)。

如果你想做基于Kerberos的单一登录,假设你的用户通过域验证,你也可以使用Java GSS-API码。我会发布一个代码示例,但我仍然需要将我的可怕原型变成适合人眼的东西。检查出some code from SpringSource的一些灵感。

如果你正在寻找NTLM(我被理解为不太安全)或其他东西,那么祝你好运。

+0

非常有用的博客条目。谢谢! – Aerse 2017-08-03 10:31:07

0

我建议你看看oVirt项目的adbroker包。它使用Spring-Ldap和Krb5 JAAS登录模块(使用GSSAPI)为了使用Kerberos对Ldap服务器(Active-Directory,ipa,rhds,Tivoli-DS)进行身份验证。在engine \ backend \ manager \ modules \ bll \ src \ main \ java \ org \ ovirt \ engine \ core \ bll \ adbroker找代码你可以使用git克隆版本库,或者使用gerrit链接

相关问题