好的,经过大约一天半的工作,我想通了。
我原来的做法是扩展 Spring 的 ActiveDirectoryLdapAuthenticationProvider 类,并覆盖它的 loadUserAuthorities() 方法,从而自定义认证用户权限的构建方式。由于不明显的原因,ActiveDirectoryLdapAuthenticationProvider 类被指定为final,所以我当然不能扩展它。
谢天谢地,开源提供了黑客攻击(并且该类的超类不是final),所以我只是复制了它的全部内容,删除了final 名称,并调整了相应的包和类引用。我没有在这个类中编辑任何代码,除了添加一个高度可见的注释,说不要编辑它。然后我在OverrideActiveDirectoryLdapAuthenticationProvider 中扩展了这个类,我也在我的ldap.xml 文件中引用了它,并在其中为loadUserAuthorities 添加了一个覆盖方法。通过未加密会话(在隔离的虚拟服务器上)上的简单 LDAP 绑定,所有这些都非常有效。
真实的网络环境要求所有的 LDAP 查询都以 TLS 握手开始,但是,被查询的服务器不是 PDC——它的名称是 'sub.domain.tld',但用户通过了正确的身份验证域.tld。此外,必须在用户名前面加上“NT_DOMAIN\”才能进行绑定。所有这些都需要定制工作,不幸的是,我在任何地方都没有找到任何帮助。
所以这里有一些荒谬的简单更改,所有这些都涉及OverrideActiveDirectoryLdapAuthenticationProvider 中的进一步覆盖:
@Override
protected DirContext bindAsUser(String username, String password) {
final String bindUrl = url; //super reference
Hashtable<String,String> env = new Hashtable<String,String>();
env.put(Context.SECURITY_AUTHENTICATION, "simple");
//String bindPrincipal = createBindPrincipal(username);
String bindPrincipal = "NT_DOMAIN\\" + username; //the bindPrincipal() method builds the principal name incorrectly
env.put(Context.SECURITY_PRINCIPAL, bindPrincipal);
env.put(Context.PROVIDER_URL, bindUrl);
env.put(Context.SECURITY_CREDENTIALS, password);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxtFactory");
//and finally, this simple addition
env.put(Context.SECURITY_PROTOCOL, "tls");
//. . . try/catch portion left alone
}
也就是说,我对这个方法所做的只是改变了bindPrincipal 字符串的格式,并在哈希表中添加了一个键/值。
我不必从传递给我的班级的domain 参数中删除子域,因为这是由ldap.xml 传递的;我只是将参数 there 更改为 <constructor-arg value="domain.tld"/>
然后我改了OverrideActiveDirectoryLdapAuthenticationProvider中的searchForUser()方法:
@Override
protected DirContextOperations searchForUser(DirContext ctx, String username) throws NamingException {
SearchControls searchCtls = new SearchControls();
searchCtls.setSearchScope(SearchControls.SUBTREE_SCOPE);
//this doesn't work, and I'm not sure exactly what the value of the parameter {0} is
//String searchFilter = "(&(objectClass=user)(userPrincipalName={0}))";
String searchFilter = "(&(objectClass=user)(userPrincipalName=" + username + "@domain.tld))";
final String bindPrincipal = createBindPrincipal(username);
String searchRoot = rootDn != null ? rootDn : searchRootFromPrincipal(bindPrincipal);
return SpringSecurityLdapTemplate.searchForSingleEntryInternal(ctx, searchCtls, searchRoot, searchFilter, new Object[]{bindPrincipal});
最后一次更改是createBindPrincipal() 方法,以正确构建字符串(出于我的目的):
@Override
String createBindPrincipal(String username) {
if (domain == null || username.toLowerCase().endsWith(domain)) {
return username;
}
return "NT_DOMAIN\\" + username;
}
通过上述更改——仍然需要从我的所有测试和 headdesking 中清理——我能够在网络上对 Active Directory 进行绑定和身份验证,捕获我希望的任何用户对象字段,识别组成员身份等。
哦,显然 TLS 不需要 'ldaps://',所以我的 ldap.xml 只需 ldap://192.168.0.3:389。
tl;dr:
要启用 TLS,请复制 Spring 的 ActiveDirectoryLdapAuthenticationProvider 类,删除 final 名称,在自定义类中扩展它,并通过将 env.put(Context.SECURITY_PROTOCOL, "tls"); 添加到环境哈希表来覆盖 bindAsUser()。就是这样。
要更紧密地控制绑定用户名、域和 LDAP 查询字符串,请酌情覆盖适用的方法。就我而言,我无法确定 {0} 的值是什么,所以我将其完全删除并插入了传递的 username 字符串。
希望有人会觉得这很有帮助。