问题
In my testing env, I have setup two active directory forests A and B, each has one domain controller and the forests have two way trust setup.
I have users - userA in forestA and userB in forestB.
I have used adfind.exe to test if both userA and userB can authenticate by hitting the forestA endpoint, like .\AdFind.exe -h -u userA@forestA.com -up .\AdFind.exe -h -u userB@forestA.com -up
Both users can authenticate through forestA as expected, as they have two way trust setup.
However, userB can never authenticate with the Java JNDI APIs. I've been searching all around, the most I could find is that they suggest to use DN or UPN for the Context.SECURITY_PRINCIPAL attribute, and none of them worked for me.
And I'm pretty sure that the principle and credentials are correct as userB can authenticate fine if the host is changed to forestB where the user is stored.
Here is the standard JNDI code that I use -
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://forestA.com");
//env.put(Context.SECURITY_PRINCIPAL, "userB@forestB.com");
//env.put(Context.SECURITY_PRINCIPAL,
CN=userB,OU=marketing,DC=forestB,DC=com");
env.put(Context.SECURITY_PRINCIPAL, "forestB\\userB");
env.put(Context.SECURITY_CREDENTIALS, "mypass");
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.REFERRAL, "follow");
DirContext dirContext = new InitialDirContext(env);
I've been stuck on this for days and appreciate for any help!
回答1:
After days of researches and digging into this issue, I've finally found a solution; hopefully it will be beneficial to those who have been struggling like me :) ...
First of all, let me correct the adfind example that I used above, I had a typo there and it should be the following -
.\AdFind.exe -h forestA.com -u userA@forestA.com -up pass
.\AdFind.exe -h forestA.com -u userB@forestB.com -up pass
Both adfind commands will pass as expected. However,
.\AdFind.exe -h forestA.com -u userB@forestB.com -up pass -simple
this will fail as it's forced to use 'simple' authentication. And that made me think, adfind by default is using others, like Kerberos authentication and 'simple' is not for cross forest authentication.
I started to change my Java JNDI code from 'simple' to Kerberos(GSSAPI) and that worked!! Make sure your realm name is in captial case and the ldap URL has to be the hostname. I struggled quiet a bit on getting that to work too.
I've included all the sample code and config files, they are all you need to get it to work.
JndiAction.java
public class JndiAction implements java.security.PrivilegedAction {
private String url;
private DirContext ctx;
public Object run() {
performJndiOperation();
return null;
}
private void performJndiOperation(/*String[] args*/) {
// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://server01.forestb.com");
// Request the use of the "GSSAPI" SASL mechanism
// Authenticate by using already established Kerberos credentials
env.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
try {
/* Create initial context */
ctx = new InitialDirContext(env);
System.out.println("Authenticated!");
// Close the context when we're done
ctx.close();
} catch (NamingException e) {
e.printStackTrace();
}
}
}
GssExample.java
public class GssExample {
public static void main(String[] args) {
System.setProperty("sun.security.debug", "true");
System.setProperty("sun.security.krb5.debug", "true");
System.setProperty("java.security.krb5.conf", "C:\\dev\\active_directory\\krb5.conf");
System.setProperty("java.security.auth.login.config", "C:\\dev\\active_directory\\jaas.conf");
// 1. Log in (to Kerberos)
LoginContext lc = null;
try {
SampleCallbackHandler cbHandler = new SampleCallbackHandler();
cbHandler.setUsername("userb@FORESTB.COM");
cbHandler.setPassword("mypass");
lc = new LoginContext(GssExample.class.getName(), cbHandler);
// Attempt authentication
lc.login();
} catch (Exception le) {
le.printStackTrace();
System.exit(-1);
}
// 2. Perform JNDI work as logged in subject
Subject.doAs(lc.getSubject(), new JndiAction());
}
}
SampleCallbackHandler.java
public class SampleCallbackHandler implements CallbackHandler {
private String username = "";
private String password = "";
public void handle(Callback[] callbacks)
throws java.io.IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
NameCallback cb = (NameCallback)callbacks[i];
cb.setName(this.username);
} else if (callbacks[i] instanceof PasswordCallback) {
PasswordCallback cb = (PasswordCallback)callbacks[i];
String pw = this.password;
char[] passwd = new char[pw.length()];
pw.getChars(0, passwd.length, passwd, 0);
cb.setPassword(passwd);
} else {
throw new UnsupportedCallbackException(callbacks[i]);
}
}
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
krb5.conf
[libdefaults]
default_realm = FORESTB.COM
[realms]
FORESTA.COM = {
kdc = foresta.com
}
FORESTB.COM = {
kdc = forestb.com
}
jaas.conf
GssExample {
com.sun.security.auth.module.Krb5LoginModule required
client=TRUE
debug=true
useTicketCache=true
principal=david;
};
来源:https://stackoverflow.com/questions/51217894/java-jndi-api-user-cannot-authenticate-to-ads-with-multi-forests-setup