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 :-)
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.