如何通过LDAP over TLS对Active Directory进行身份验证?

cab*_*ery 4 java ldap active-directory spring-security spring-security-ldap

我有一个工作概念验证应用程序,它可以在测试服务器上通过LDAP成功验证Active Directory,但生产应用程序必须通过TLS进行验证 - 域控制器关闭任何不通过TLS启动的连接.

我已经在Eclipse中安装了LDAP浏览器,我确实可以在其中使用TLS绑定,但我不能在我的生活中弄清楚如何让我的应用程序使用TLS.

ldap.xml:

<bean id="ldapAuthenticationProvider"
        class="my.project.package.OverrideActiveDirectoryLdapAuthenticationProvider">

    <!-- this works to authenticate by binding as the user in question -->
    <constructor-arg value="test.server"/>
    <constructor-arg value="ldap://192.168.0.2:389"/>

    <!-- this doesn't work, because the server requires a TLS connection -->
    <!-- <constructor-arg value="production.server"/> -->
    <!-- <constructor-arg value="ldaps://192.168.0.3:389"/> -->

    <property name="convertSubErrorCodesToExceptions" value="true"/>
</bean>
Run Code Online (Sandbox Code Playgroud)

OverrideActiveDirectoryLdapAuthenticationProvider是一个覆盖类,它扩展了Spring ActiveDirectoryLdapAuthenticationProvider类的副本,由于某种原因被指定final.我覆盖的原因与自定义用户对象上填充权限/权限的方式有关(我们将使用相关组的组成员身份来构建用户的权限,或者我们将从AD用户对象的字段中读取).在其中,我只是重写loadUserAuthorities()方法,但我怀疑我可能还需要覆盖bindAsUser()方法或可能的doAuthentication()方法.

XML和一个覆盖类是我的应用程序管理身份验证的唯一两个地方,而不是让Spring完成工作.我已经阅读了几个要启用TLS的地方我需要扩展DefaultTlsDirContextAuthenticationStrategy类,但是我在哪里连接它?是否有命名空间解决方案?我是否需要完全做其他事情(即放弃使用Spring ActiveDirectoryLdapAuthenticationProvider而改为使用LdapAuthenticationProvider)?

任何帮助表示赞赏.

cab*_*ery 6

好的,所以经过大约一天半的工作,我发现了.

我最初的方法是扩展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
}
Run Code Online (Sandbox Code Playgroud)

也就是说,我对此方法所做的只是改变了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});
Run Code Online (Sandbox Code Playgroud)

最后一个更改是该createBindPrincipal()方法,正确构建String(为了我的目的):

@Override
String createBindPrincipal(String username) {
    if (domain == null || username.toLowerCase().endsWith(domain)) {
        return username;
    }
    return "NT_DOMAIN\\" + username;
}
Run Code Online (Sandbox Code Playgroud)

通过以上更改 - 仍然需要从我的所有测试和漫游中清除 - 我能够在网络上绑定和验证自己对抗Active Directory,捕获我希望的任何用户对象字段,识别组成员身份等

哦,显然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字符串.

希望有人在那里发现这有用.