Java AD Authentication across Trusted Domains
- by benjiisnotcool
I am trying to implement Active Directory authentication in Java which will be ran from a Linux machine. Our AD set-up will consist of multiple servers that share trust relationships with one another so for our test environment we have two domain controllers:
test1.ad1.foo.com who trusts test2.ad2.bar.com.
Using the code below I can successfully authenticate a user from test1 but not on test2:
public class ADDetailsProvider implements ResultSetProvider {
private String domain;
private String user;
private String password;
public ADDetailsProvider(String user, String password) {
//extract domain name
if (user.contains("\\")) {
this.user = user.substring((user.lastIndexOf("\\") + 1), user.length());
this.domain = user.substring(0, user.lastIndexOf("\\"));
} else {
this.user = user;
this.domain = "";
}
this.password = password;
}
/* Test from the command line */
public static void main (String[] argv) throws SQLException {
ResultSetProvider res = processADLogin(argv[0], argv[1]);
ResultSet results = null;
res.assignRowValues(results, 0);
System.out.println(argv[0] + " " + argv[1]);
}
public boolean assignRowValues(ResultSet results, int currentRow)
throws SQLException
{
// Only want a single row
if (currentRow >= 1) return false;
try {
ADAuthenticator adAuth = new ADAuthenticator();
LdapContext ldapCtx = adAuth.authenticate(this.domain, this.user, this.password);
NamingEnumeration userDetails = adAuth.getUserDetails(ldapCtx, this.user);
// Fill the result set (throws SQLException).
while (userDetails.hasMoreElements()) {
Attribute attr = (Attribute)userDetails.next();
results.updateString(attr.getID(), attr.get().toString());
}
results.updateInt("authenticated", 1);
return true;
} catch (FileNotFoundException fnf) {
Logger.getAnonymousLogger().log(Level.WARNING,
"Caught File Not Found Exception trying to read cris_authentication.properties");
results.updateInt("authenticated", 0);
return false;
} catch (IOException ioe) {
Logger.getAnonymousLogger().log(Level.WARNING,
"Caught IO Excpetion processing login");
results.updateInt("authenticated", 0);
return false;
} catch (AuthenticationException aex) {
Logger.getAnonymousLogger().log(Level.WARNING,
"Caught Authentication Exception attempting to bind to LDAP for [{0}]",
this.user);
results.updateInt("authenticated", 0);
return true;
} catch (NamingException ne) {
Logger.getAnonymousLogger().log(Level.WARNING,
"Caught Naming Exception performing user search or LDAP bind for [{0}]",
this.user);
results.updateInt("authenticated", 0);
return true;
}
}
public void close() {
// nothing needed here
}
/**
* This method is called via a Postgres function binding to access the
* functionality provided by this class.
*/
public static ResultSetProvider processADLogin(String user, String password) {
return new ADDetailsProvider(user, password);
}
}
public class ADAuthenticator {
public ADAuthenticator()
throws FileNotFoundException, IOException {
Properties props = new Properties();
InputStream inStream = this.getClass().getClassLoader().
getResourceAsStream("com/bar/foo/ad/authentication.properties");
props.load(inStream);
this.domain = props.getProperty("ldap.domain");
inStream.close();
}
public LdapContext authenticate(String domain, String user, String pass)
throws AuthenticationException, NamingException, IOException {
Hashtable env = new Hashtable();
this.domain = domain;
env.put(Context.INITIAL_CONTEXT_FACTORY, com.sun.jndi.ldap.LdapCtxFactory);
env.put(Context.PROVIDER_URL, "ldap://" + test1.ad1.foo.com + ":" + 3268);
env.put(Context.SECURITY_AUTHENTICATION, simple);
env.put(Context.REFERRAL, follow);
env.put(Context.SECURITY_PRINCIPAL, (domain + "\\" + user));
env.put(Context.SECURITY_CREDENTIALS, pass);
// Bind using specified username and password
LdapContext ldapCtx = new InitialLdapContext(env, null);
return ldapCtx;
}
public NamingEnumeration getUserDetails(LdapContext ldapCtx, String user)
throws NamingException {
// List of attributes to return from LDAP query
String returnAttributes[] = {"ou", "sAMAccountName", "givenName", "sn", "memberOf"};
//Create the search controls
SearchControls searchCtls = new SearchControls();
searchCtls.setReturningAttributes(returnAttributes);
//Specify the search scope
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
// Specify the user to search against
String searchFilter = "(&(objectClass=*)(sAMAccountName=" + user + "))";
//Perform the search
NamingEnumeration answer = ldapCtx.search("dc=dev4,dc=dbt,dc=ukhealth,dc=local", searchFilter, searchCtls);
// Only care about the first tuple
Attributes userAttributes = ((SearchResult)answer.next()).getAttributes();
if (userAttributes.size() <= 0) throw new NamingException();
return (NamingEnumeration) userAttributes.getAll();
}
From what I understand of the trust relationship, if trust1 receives a login attempt for a user in trust2, then it should forward the login attempt on to it and it works this out from the user's domain name.
Is this correct or am I missing something or is this not possible using the method above?
--EDIT--
The stack trace from the LDAP bind is
{java.naming.provider.url=ldap://test1.ad1.foo.com:3268, java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, java.naming.security.authentication=simple, java.naming.referral=follow}
30-Oct-2012 13:16:02
ADDetailsProvider assignRowValues
WARNING: Caught Authentication Exception attempting to bind to LDAP for [trusttest]
Auth error is [LDAP: error code 49 - 80090308: LdapErr: DSID-0C0903A9, comment: AcceptSecurityContext error, data 52e, v1db0]