在Linux上使用Java对Active Directory进行身份validation
我有一个使用Java对Active Directory进行身份validation的简单任务。 只是validation凭证,没有别的。 假设我的域是“fun.xyz.tld”,OUpath是未知的,并且用户名/密码是testu / testp。
我知道有几个Java库可以简化这个任务,但是我没有成功实现它们。 我发现的大多数示例都是针对LDAP,而不是专门针对Active Directory。 发出LDAP请求意味着发送一个OUpath,我没有。 此外,发出LDAP请求的应用程序应该已经绑定到Active Directory以访问它…不安全,因为凭据将不得不被存储在某个地方可发现。 如果可能的话,我想用testing凭证进行testing绑定 – 这意味着该帐户是有效的。
最后,如果可能的话,有没有办法使这样的authentication机制encryption? 我知道AD使用Kerberos,但不确定Java的LDAP方法。
有没有人有一个工作代码的例子? 谢谢。
在Linux或任何其他平台上,有三种身份validation协议可用于在Java和Active Directory之间执行身份validation(这些身份validation协议不仅限于HTTP服务):
-
Kerberos – Kerberos提供单点login(SSO)和委托,但Web服务器也需要SPNEGO支持才能通过IE接受SSO。
-
NTLM – NTLM通过IE(以及其他浏览器,如果configuration正确)支持SSO。
-
LDAP – 可以使用LDAP绑定来简单validation帐户名称和密码。
还有一种叫做“ADFS”的技术,它为使用SAML的网站提供了SSO,这个网站调用了Windows SSP,所以在实践中它基本上是一个使用其他协议的迂回方式。
每个协议都有它的优点,但作为一个经验法则,为了达到最大的兼容性,你通常应该尽量“像Windows那样做”。 那么Windows是做什么的?
首先,两台Windows计算机之间的authentication支持Kerberos,因为服务器不需要与DC通信,客户端可以cachingKerberos票据,从而减lessDC上的负载(因为Kerberos支持委派)。
但是,如果authentication双方都不具有域帐户,或者如果客户端不能与DC通信,则需要NTLM。 所以Kerberos和NTLM并不是相互排斥的,NTLM也不会被Kerberos所淘汰。 事实上,在某些方面,NTLM比Kerberos更好。 请注意,当提到Kerberos和NTLM时,我还要提到SPENGO和集成Windows身份validation(IWA)。 IWA是一个简单的术语,基本上是指Kerberos或NTLM或SPNEGO来协商Kerberos或NTLM。
使用LDAP绑定作为validation凭据的方式效率不高,需要使用SSL。 但直到最近,实施Kerberos和NTLM都很困难,所以使用LDAP作为转换authentication服务一直存在。 但是在这一点上通常应该避免。 LDAP是一个信息目录,而不是一个validation服务。 用它来达到预期的目的。
那么,如何在Java中实现Kerberos或NTLM,特别是在Web应用程序的上下文中?
有许多像Quest Software和Centrify这样的大公司都有专门提到Java的解决scheme。 我不能评论这些,因为它们是公司范围内的“身份pipe理解决scheme”,所以从他们网站的市场营销angular度来看,很难确切地说明使用什么协议以及如何使用协议。 你需要联系他们的细节。
在Java中实现Kerberos并不难,因为标准Java库通过org.ietf.gssapi类支持Kerberos。 然而,直到最近才有一个主要的障碍 – IE不发送原始的Kerberos令牌,它发送SPNEGO令牌。 但是对于Java 6,SPNEGO已经实现。 理论上你应该能够编写一些可以validationIE客户端的GSSAPI代码。 但是我没有尝试过。 Kerberos的Sun实现多年来一直是一个错误的喜剧,所以基于Sun在这个领域的logging,我不会对他们的SPENGO实现做任何承诺,直到你有了那只鸟。
对于NTLM,有一个名为JCIFS的免费OSS项目,它具有NTLM HTTP身份validationServlet筛选器。 但是,它使用中间人方法来validation与NTLMv2不兼容的SMB服务器(它正在慢慢地成为必需的域安全策略)来validation凭据。 出于这个原因和其他原因,JCIFS的HTTPfilter部分计划被删除。 请注意,使用JCIFS实施相同技术的分拆数量很多。 因此,如果您看到其他宣称支持NTLM SSO的项目,请检查细则。
使用Active DirectoryvalidationNTLM凭据的唯一正确方法是使用带有Secure Channel的NETLOGON的NetrLogonSamLogon DCERPC调用。 Java中存在这样的事情吗? 是。 这里是:
http://www.ioplex.com/jespa.html
Jespa是100%的Java NTLM实现,支持NTLMv2,NTLMv1,完整的完整性和机密性选项以及上述NETLOGON凭证validation。 它包括HTTP SSO筛选器,JAAS LoginModule,HTTP客户端,SASL客户端和服务器(具有JNDI绑定),用于创build自定义NTLM服务的通用“安全提供程序”等等。
麦克风
以下是我根据本博客中的示例编写的代码: 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; //import org.acegisecurity.AuthenticationException; import org.acegisecurity.BadCredentialsException; import org.acegisecurity.GrantedAuthority; import org.acegisecurity.GrantedAuthorityImpl; import org.acegisecurity.providers.AuthenticationProvider; import org.acegisecurity.providers.UsernamePasswordAuthenticationToken; import org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider; import org.acegisecurity.userdetails.UserDetails; import org.acegisecurity.userdetails.UserDetailsService; import org.acegisecurity.userdetails.UsernameNotFoundException; 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<GrantedAuthority> groups = new ArrayList<GrantedAuthority>(); 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(new GrantedAuthorityImpl(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().toString()); } } 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(); } }
我刚刚完成了一个使用AD和Java的项目。 我们使用了Spring ldapTemplate。
AD是LDAP兼容(几乎),我不认为你会有任何问题,你有。 我的意思是,这是AD或任何其他LDAP服务器的事实,如果你只是想连接它并不重要。
我会看看: Spring LDAP
他们也有例子。
至于encryption,我们使用SSL连接(所以它是LDAPS)。 AD必须在SSL端口/协议上进行configuration。
但首先,确保您可以通过LDAP IDE正确连接到您的AD。 我使用Apache Directory Studio ,它非常酷,而且是用Java编写的。 这就是我所需要的。 出于testing目的,您也可以安装Apache Directory Server
你只是validation凭据? 在这种情况下,你可以做简单的kerberos,而不用打扰LDAP。
正如ioplex和其他人所说,有很多select。 要使用LDAP(以及Novell LDAP API)进行身份validation,我已经使用了类似以下内容:
LDAPConnection connection = new LDAPConnection( new LDAPJSSEStartTLSFactory() ); connection.connect(hostname, port); connection.startTLS(); connection.bind(LDAPConnection.LDAP_V3, username+"@"+domain, password.getBytes());
作为“特殊function”,Active Directory允许LDAP绑定“user @ domain”而不使用帐户的可分辨名称。 此代码使用StartTLS在连接上启用TLSencryption; 另一种select是LDAP over SSL,这是我的 AD服务器不支持的。
真正的诀窍在于定位服务器和主机; 官方的方法是使用DNS SRV(服务)logging查找来定位一组候选主机,然后执行基于UDP的LDAP“ping”(以特定的Microsoft格式)来find正确的服务器。 如果你有兴趣,我已经发布了一些关于我在这个领域冒险和发现旅程的博客文章 。
如果你想做基于Kerberos的用户名/密码authentication,你正在看另一个水壶; 它可以用Java GSS-API代码实现,尽pipe我不确定它是否执行validation身份validation的最后一步。 (执行validation的代码可以联系AD服务器来检查用户名和密码,这将导致为用户授予票证,但要确保AD服务器未被模拟,还需要尝试获取票据用户自己,这是比较复杂的。)
如果您想要执行基于Kerberos的单点login,假设您的用户已通过域身份validation,则可以使用Java GSS-API代码来执行此操作。 我会张贴一个代码示例,但我仍然需要把我的可怕原型变成适合人眼的东西。 查看SpringSource的一些代码,获取一些灵感。
如果你正在寻找NTLM(我被理解为不太安全)或其他东西,那么祝你好运。
如果你所要做的只是使用Kerberos对AD进行身份validation,那么一个简单的http://spnego.sourceforge.net/HelloKDC.java程序应该这样做。;
看一下项目的“飞行前”文档,其中介绍了HelloKDC.java程序。
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
没有SSL的ldapauthentication是不安全的,任何人都可以查看用户凭证,因为ldap客户端在ldap绑定操作期间传输usernamae和密码所以总是使用ldaps协议。 来源: Ldap身份validationJava Spring Security中的活动目录(带有示例)
我build议你看一下oVirt项目的adbroker包。 它使用Spring-Ldap和Krb5 JAASlogin模块(使用GSSAPI)为了使用Kerberos对Ldap服务器(Active-Directory,ipa,rhds,Tivoli-DS)进行身份validation。 在engine \ backend \ manager \ modules \ bll \ src \ main \ java \ org \ ovirt \ engine \ core \ bll \ adbroker中查找代码
你可以使用git克隆版本库或者使用gerrit链接浏览