How to authenticate against Active Directory via LDAP over TLS?

喜你入骨 提交于 2019-11-29 07:51:32

Okay, so after about a day and a half of working on it, I figured it out.

My original approach was to extend Spring's ActiveDirectoryLdapAuthenticationProvider class, and override its loadUserAuthorities() method, so as to customize the way the authenticated user's permissions were built. For unobvious reasons, the ActiveDirectoryLdapAuthenticationProvider class is designated as final, so of course I cannot extend it.

Thankfully, open source provides for hacking (and that class' superclasses are not final), so I simply copied the entire contents of it, removed the final designation, and adjusted the package and class references accordingly. I did not edit any code in this class, except to add a highly visible comment which says not to edit it. I then extended this class in OverrideActiveDirectoryLdapAuthenticationProvider, which I also referenced in my ldap.xml file, and in it added an override method for loadUserAuthorities. All of that worked great with a simple LDAP bind over an unencrypted session (on an isolated virtual server).

The real network environment requires that all LDAP queries begin with a TLS handshake, however, and the server being queried is not the PDC -- its name is 'sub.domain.tld`, but the user is properly authenticated against 'domain.tld.' Also, the username must be prepended with 'NT_DOMAIN\' in order to bind. All of this required customization work, and unfortunately, I found little or no help anywhere.

So here are the preposterously simple changes, all of which involve further overrides in 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
}

That is, all I did to this method was change the way the bindPrincipal string was formatted, and I added a key/value to the hashtable.

I did not have to remove the subdomain from the domain parameter passed to my class, because that was being passed by ldap.xml; I simply changed the parameter there to <constructor-arg value="domain.tld"/>

Then I changed the searchForUser() method in 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});

The last change was to the createBindPrincipal() method, to build the String properly (for my purposes):

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

And with the above changes -- which still need cleaned up from all of my testing and headdesking -- I was able to bind and authenticate as myself against Active Directory on the network-proper, capture whatever user object fields I wished, identify group membership, etc.

Oh, and apparently TLS does not require 'ldaps://', so my ldap.xml simply has ldap://192.168.0.3:389.


tl;dr:

To enable TLS, copy Spring's ActiveDirectoryLdapAuthenticationProvider class, remove the final designation, extend it in a custom class, and override bindAsUser() by adding env.put(Context.SECURITY_PROTOCOL, "tls"); to the environment hashtable. That's it.

To control more closely the bind username, the domain, and the LDAP querystring, override the applicable methods as appropriate. In my case, I could not identify just what the value of {0} was, so I removed it entirely and inserted the passed username string instead.

Hopefully, someone out there finds this helpful.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!