问题
I'm trying to bind to an Active Directory server with GSS on a Windows computer that is logged in as a domain user. Normally, this works fine. But when signing and binding is being enforced
(see https://support.microsoft.com/en-us/help/4520412/2020-ldap-channel-binding-and-ldap-signing-requirements-for-windows)
GSS fails to bind. According to https://bugs.openjdk.java.net/browse/JDK-8245527, this feature was added in JDK 16 (b18), but I have been unable to successfully bind failing with the error message
javax.naming.AuthenticationException: [LDAP: error code 49 - 80090346: LdapErr: DSID-0C090595, comment: AcceptSecurityContext error, data 80090346, v3839
Other relevant details:
My company has 3 domain controllers and each returns the same error. They are currently set to value=1 (when supported), though value=2 (always) has the same effect.
I'm fairly certain that my project is using JDK 16 build 20.
When binding with a username and password, all works as expected.
Additionally, binding via LDAP (not LDAPS) works with GSS.
The critical combination is GSS + LDAPS.
EDIT I get the same behavior in JDK 15.0.1.9 as I do in JDK 16. This makes me think that the functionality is not implemented fully in JDK16b20 however after inspecting the included source code I can see where the code was added.
Below is the code being used to connect to the server:
public class ActiveDirectory {
public LdapContext getConnection(boolean ssl) throws NamingException {
return getConnection(null, null, ssl);
}
public LdapContext getConnection(String username, String password, boolean ssl) throws NamingException {
String domainName = "";
try {
String fqdn = java.net.InetAddress.getLocalHost().getCanonicalHostName(); //lookup the domain
if (fqdn.split("\\.").length > 1) {
domainName = fqdn.substring(fqdn.indexOf(".") + 1);
}
} catch (java.net.UnknownHostException e) {
}
Hashtable props = new Hashtable();
if (password != null) {
password = password.trim();
if (password.length() == 0) { //the password was blank. Bind with GSS anyway
password = null;
}
}
if (password != null) { // a password was provided. Bind as that user
String principalName = username + "@" + domainName;
props.put(Context.SECURITY_PRINCIPAL, principalName);
props.put(Context.SECURITY_AUTHENTICATION, "simple");
props.put(Context.SECURITY_CREDENTIALS, password);
} else { //no password. Bind with GSS
String principalName = System.getProperty("user.name") + "@" + domainName;
username=System.getProperty("user.name");
props.put(Context.SECURITY_PRINCIPAL, principalName);
props.put(Context.SECURITY_AUTHENTICATION, "GSSAPI");
System.setProperty("sun.security.jgss.native", "true");
}
props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
String ldapURL = "";
for (String server : domainControllers) {
if (ssl) { //use SSL to bind
ldapURL = "ldaps://" + server + "." + domainName + "/";
props.put(Context.SECURITY_PROTOCOL, "ssl");
props.put("com.sun.jndi.ldap.tls.cbtype", "tls-server-end-point"); //use channel binding !!! DOESNT WORK WITH GSS. See https://bugs.openjdk.java.net/browse/JDK-8245527?attachmentViewMode=gallery
props.put("com.sun.jndi.ldap.connect.timeout", "2000");
} else { //dont use ssl to bind
ldapURL = "ldap://" + server + "." + domainName + '/';
props.put(Context.SECURITY_PROTOCOL, "");
}
props.put(Context.PROVIDER_URL,ldapURL);
try {
return connect(props);
} catch (NamingException e) {
System.out.println(e.toString());
}
}
throw new NamingException("Failed to authenticate " + username + "@" + domainName);
}
private LdapContext connect(Hashtable props) throws NamingException {
return new InitialLdapContext(props, null);
}
If anyone can spot what I'm doing wrong, that would be much appreciated. Thanks!
来源:https://stackoverflow.com/questions/64392108/connecting-to-ldaps-with-gss-and-channel-binding