How to get additional user attributes from LDAP in Spring Security?

Alex picture Alex · Jun 9, 2016 · Viewed 12.6k times · Source

I'm currently trying to develop a Spring Boot application whose purpose it will be to manage user entries in our LDAP directory.

LDAP login already works; so does the lookup of groups a user is member of.

Additionally, I'd like to populate the Spring Security Principal object with some more LDAP attributes of that user. I've read several posts on SO and also the official Spring documentation, but simply cannot get this to work.

Here is my current code: Class AuthenticationConfiguration

@Configuration
class AuthenticationConfiguration extends GlobalAuthenticationConfigurerAdapter {

    private String ldapUrl = "ldap://127.0.0.1:10389/dc=corp,dc=org";
    private String bindUser = "cn=spring,ou=users,dc=corp,dc=org";
    private String bindPW = "<password>";
    private String groupSearchBase = "ou=groups";
    private String groupSearchFilter = "(member={0})";
    private String userDnPattern = "uid={0},ou=users";
    private Log log = LogFactory.getLog(this.getClass());

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {

        DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapUrl);

        contextSource.setUserDn(bindUser);
        contextSource.setPassword(bindPW);
        contextSource.afterPropertiesSet();
        log.info(contextSource.getReadOnlyContext().getAttributes("uid=testuser,ou=users")); // returns all LDAP attributes from that user

        DefaultLdapAuthoritiesPopulator populator = new DefaultLdapAuthoritiesPopulator(contextSource, groupSearchBase);
        populator.setGroupSearchFilter(groupSearchFilter);
        populator.setSearchSubtree(true);
        populator.setIgnorePartialResultException(true);

        auth
        .ldapAuthentication()
        .ldapAuthoritiesPopulator(populator)
        .contextSource(contextSource)
        .userDetailsContextMapper(userDetailsContextMapper())
        .userDnPatterns(userDnPattern)
        ;
    }

    @Bean
    public UserDetailsContextMapper userDetailsContextMapper() {
        return new CustomUserDetailsContextMapper();
    }

}

As you can see, I'm using the userDetailsContextMapper() method to return an instance of my CustomUserDetailsContextMapper:

@Configuration
public class CustomUserDetailsContextMapper extends LdapUserDetailsMapper implements UserDetailsContextMapper {

    private Log log = LogFactory.getLog(this.getClass());

    @Override
    public LdapUserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {

        LdapUserDetailsImpl details = (LdapUserDetailsImpl) super.mapUserFromContext(ctx, username, authorities);
        log.info("DN from ctx: " + ctx.getDn()); // return correct DN
        log.info("Attributes size: " + ctx.getAttributes().size()); // always returns 0

        return new CustomUserDetails(details);
    }

    @Override
    public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
        // default
    }
}

Now, when you look at that first log statement in AuthenticationConfiguration, Spring will actually print the whole user object to stdout, correctly showing all LDAP attributes from the user: {displayname=displayName: Test User, givenname=givenName: Test, objectclass=objectClass: posixAccount, top [...]}

However, the log statements in my CustomUserDetailsContextMapper class do not. Although the first one correctly displays the DN of the logged in user, the second one just display 0, i.e. ctx does not seem to contain any attributes from the current user.

I also tried directly querying for attributes via ctx.getAttribute("attribute"), ctx.getStringAttribute("attribute") or ctx.getObjectAttribute("attribute"), to no avail.

How can I access LDAP attributes from within the mapUserFromContext method?

I'm really out of ideas, so any help would be greatly appreciated :-)

Answer

Alex picture Alex · Jun 10, 2016

Solved it. The problem was not in the application code, but in the LDAP configuration.

Because I was using the default BindAuthenticator, Spring Security tried to bind to LDAP with the user specified in the login form. Unfortunately, all users under ou=users only had a search permission in the LDAP configuration. Changing search to read (see point 8.2.3 http://www.openldap.org/doc/admin24/access-control.html) solved this issue for me.

Note that the login/bind itself was still successful (because the auth permission is a subset of search), but any retrieval of attributes failed, as this required the read permission.