好的,所以经过大约一天半的工作后,我想通了。
我最初的方法是扩展 Spring 的ActiveDirectoryLdapAuthenticationProvider
类,并覆盖它的loadUserAuthorities()
方法,以便自定义构建经过身份验证的用户权限的方式。由于不明显的原因,ActiveDirectoryLdapAuthenticationProvider
该类被指定为final
,所以我当然不能扩展它。
值得庆幸的是,开源提供了黑客攻击(并且该类的超类不是 final
),所以我只是复制了它的全部内容,删除了final
名称,并相应地调整了包和类引用。我没有在这个类中编辑任何代码,除了添加一个高度可见的注释,说不要编辑它。然后我扩展了这个类OverrideActiveDirectoryLdapAuthenticationProvider
,我也在我的ldap.xml
文件中引用了它,并在其中添加了一个覆盖方法loadUserAuthorities
。通过未加密会话(在隔离的虚拟服务器上)上的简单 LDAP 绑定,所有这些都非常有效。
然而,真实的网络环境要求所有 LDAP 查询都以 TLS 握手开始,并且被查询的服务器不是 PDC——它的名称是“sub.domain.tld”,但用户已针对“domain.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
; 我只是将那里的参数更改为<constructor-arg value="domain.tld"/>
然后我改变了searchForUser()
方法OverrideActiveDirectoryLdapAuthenticationProvider
:
@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 进行绑定和身份验证,捕获我希望的任何用户对象字段,识别组成员身份, ETC。
哦,显然 TLS 不需要 'ldaps://',所以我ldap.xml
只需要ldap://192.168.0.3:389
.
tl;博士:
要启用 TLS,复制 Spring 的ActiveDirectoryLdapAuthenticationProvider
类,删除final
指定,在自定义类中扩展它,并bindAsUser()
通过添加env.put(Context.SECURITY_PROTOCOL, "tls");
到环境哈希表来覆盖。就是这样。
要更紧密地控制绑定用户名、域和 LDAP 查询字符串,请酌情覆盖适用的方法。就我而言,我无法确定 的值{0}
是什么,因此我将其完全删除并插入了传递的username
字符串。
希望有人会觉得这很有帮助。