Linux 下基于 Java 的 ActiveDirectory 身份验证

我有一个使用 Java 对 ActiveDirectory 进行身份验证的简单任务。只是核实证件,没别的。假设我的域是“ fun.xyz.tld”,OU 路径是未知的,用户名/密码是 testu/testp。

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

最后,如果可能的话,是否有办法使这种身份验证机制加密?我知道 AD 使用 Kerberos,但不确定 Java 的 LDAP 方法是否使用 Kerberos。

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

166936 次浏览

Are you just verifying credentials? In that case you could just do plain kerberos and not bother with LDAP.

I just finished a project that uses AD and Java. We used Spring ldapTemplate.

AD is LDAP compliant (almost), I don't think you will have any issues with the task you have. I mean the fact that it is AD or any other LDAP server it doesn't matter if you want just to connect.

I would take a look at: Spring LDAP

They have examples too.

As for encryption, we used SSL connection (so it was LDAPS). AD had to be configured on a SSL port/protocol.

But first of all, make sure you can properly connect to your AD via an LDAP IDE. I use Apache Directory Studio, it is really cool, and it is written in Java. That is all I needed. For testing purposes you could also install Apache Directory Server

Here's the code I put together based on example from this blog: LINK and this source: 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();
}


}

There are 3 authentication protocols that can be used to perform authentication between Java and Active Directory on Linux or any other platform (and these are not just specific to HTTP services):

  1. Kerberos - Kerberos provides Single Sign-On (SSO) and delegation but web servers also need SPNEGO support to accept SSO through IE.

  2. NTLM - NTLM supports SSO through IE (and other browsers if they are properly configured).

  3. LDAP - An LDAP bind can be used to simply validate an account name and password.

There's also something called "ADFS" which provides SSO for websites using SAML that calls into the Windows SSP so in practice it's basically a roundabout way of using one of the other above protocols.

Each protocol has it's advantages but as a rule of thumb, for maximum compatibility you should generally try to "do as Windows does". So what does Windows do?

First, authentication between two Windows machines favors Kerberos because servers do not need to communicate with the DC and clients can cache Kerberos tickets which reduces load on the DCs (and because Kerberos supports delegation).

But if the authenticating parties do not both have domain accounts or if the client cannot communicate with the DC, NTLM is required. So Kerberos and NTLM are not mutually exclusive and NTLM is not obsoleted by Kerberos. In fact in some ways NTLM is better than Kerberos. Note that when mentioning Kerberos and NTLM in the same breath I have to also mention SPENGO and Integrated Windows Authentication (IWA). IWA is a simple term that basically means Kerberos or NTLM or SPNEGO to negotiate Kerberos or NTLM.

Using an LDAP bind as a way to validate credentials is not efficient and requires SSL. But until recently implementing Kerberos and NTLM have been difficult so using LDAP as a make-shift authentication service has persisted. But at this point it should generally be avoided. LDAP is a directory of information and not an authentication service. Use it for it's intended purpose.

So how do you implement Kerberos or NTLM in Java and in the context of web applications in particular?

There are a number of big companies like Quest Software and Centrify that have solutions that specifically mention Java. I can't really comment on these as they are company-wide "identity management solutions" so, from looking the marketing spin on their website, it's hard to tell exactly what protocols are being used and how. You would need to contact them for the details.

Implementing Kerberos in Java is not terribly hard as the standard Java libraries support Kerberos through the org.ietf.gssapi classes. However, until recently there's been a major hurdle - IE doesn't send raw Kerberos tokens, it sends SPNEGO tokens. But with Java 6, SPNEGO has been implemented. In theory you should be able to write some GSSAPI code that can authenticate IE clients. But I haven't tried it. The Sun implementation of Kerberos has been a comedy of errors over the years so based on Sun's track record in this area I wouldn't make any promises about their SPENGO implementation until you have that bird in hand.

For NTLM, there is a Free OSS project called JCIFS that has an NTLM HTTP authentication Servlet Filter. However it uses a man-in-the-middle method to validate the credentials with an SMB server that does not work with NTLMv2 (which is slowly becoming a required domain security policy). For that reason and others, the HTTP Filter part of JCIFS is scheduled to be removed. Note that there are number of spin-offs that use JCIFS to implement the same technique. So if you see other projects that claim to support NTLM SSO, check the fine print.

The only correct way to validate NTLM credentials with Active Directory is using the NetrLogonSamLogon DCERPC call over NETLOGON with Secure Channel. Does such a thing exist in Java? Yes. Here it is:

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

Jespa is a 100% Java NTLM implementation that supports NTLMv2, NTLMv1, full integrity and confidentiality options and the aforementioned NETLOGON credential validation. And it includes an HTTP SSO Filter, a JAAS LoginModule, HTTP client, SASL client and server (with JNDI binding), generic "security provider" for creating custom NTLM services and more.

Mike

If all you want to do is authenticate against AD using Kerberos, then a simple http://spnego.sourceforge.net/HelloKDC.java program should do it.

Take a look at the project's "pre-flight" documentation which talks about the HelloKDC.java program.

As ioplex and others have said, there are many options. To authenticate using LDAP (and the Novell LDAP API), I have used something like:


LDAPConnection connection = new LDAPConnection( new LDAPJSSEStartTLSFactory() );
connection.connect(hostname, port);
connection.startTLS();
connection.bind(LDAPConnection.LDAP_V3, username+"@"+domain, password.getBytes());

As a "special feature", Active Directory allows LDAP binds against "user@domain" without using the distinguished name of the account. This code uses StartTLS to enable TLS encryption on the connection; the other alternative is LDAP over SSL, which is not supported by my AD servers.

The real trick is in locating the server and host; the official way is to use a DNS SRV (service) record lookup to locate a bundle of candidate hosts, then do a UDP-based LDAP "ping" (in a particular Microsoft format) to locate the correct server. If you are interested, I've posted some blog articles about my journey of adventure and discovery in that area.

If you want to do Kerberos-based username/password authentication, you are looking at another kettle of fish; it is doable with the Java GSS-API code, although I am not sure it performs the final step to validate the authentication. (The code doing the validation can contact the AD server to check the username and password, which results in a ticket granting ticket for the user, but to ensure the AD server is not being impersonated, it also needs to try to get a ticket for the user to itself, which is somewhat more complicated.)

If you want to do Kerberos-based single sign-on, assuming your users are authenticated to the domain, you can do that as well with the Java GSS-API code. I would post a code sample, but I still need to turn my hideous prototype into something fit for human eyes. Check out some code from SpringSource for some inspiration.

If you are looking for NTLM (which I was given to understand is less secure) or something else, well, good luck.

ldap authentication without SSL is not safe and anyone can view user credential because ldap client transfer usernamae and password during ldap bind operation So Always use ldaps protocol. source: Ldap authentication Active directory in Java Spring Security with Example

I recommend you to look at the adbroker package of the oVirt project. It uses Spring-Ldap and the Krb5 JAAS Login module (with GSSAPI) in order to authenticate using Kerberos against Ldap servers (Active-Directory, ipa, rhds, Tivoli-DS). Look for the code at engine\backend\manager\modules\bll\src\main\java\org\ovirt\engine\core\bll\adbroker

You can use git to clone the repository or browse using the gerrit link