0
votes

This question is about how to use multiple group search bases instead of the one.

I used an example provided by samaddico (simple spring security + LDAP example), modified it for single group search base with provided server / user / LDAP configuration text. It uses a service account to connect to ldap and a user which then tries to authenticate for certain simple web pages. This approach works but lacks ability to collect membership / roles from different groups in the search tree.

Spring Security provides classes LdapContextSource and MultipleLdapAuthoritiesPopulator to allow for searching for roles in different locations. Now here is the code which will result in the error shown below:

LDAP Configuration:

* Create an implementation of org.springframework.security.ldap.userdetails.LdapAuthoritiesPopulator which can call 
multiple instances of LdapAuthoritiesPopulator.
 * Then create one LdapAuthoritiesPopulatorfor each 'groupSearchBase' that I wanted to query.

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

        LdapContextSource contextSource =   contextSource();

        MultipleLdapAuthoritiesPopulator multipleLdapAuthoritiesPopulator = new MultipleLdapAuthoritiesPopulator(
        new DefaultLdapAuthoritiesPopulator(contextSource, ldapGroupSearchBaseA),
        new DefaultLdapAuthoritiesPopulator(contextSource, ldapGroupSearchBaseB),
        new DefaultLdapAuthoritiesPopulator(contextSource, ldapGroupSearchBaseC));

        auth
            .ldapAuthentication()
            .contextSource(contextSource)
            .ldapAuthoritiesPopulator(multipleLdapAuthoritiesPopulator)
            .userSearchFilter(ldapUserSearchFilter)
            .userSearchBase(ldapUserSearchBase);
    }

    class MultipleLdapAuthoritiesPopulator implements LdapAuthoritiesPopulator {
        private List<LdapAuthoritiesPopulator> authoritiesPopulators;

        public MultipleLdapAuthoritiesPopulator(LdapAuthoritiesPopulator...authoritiesPopulators) {
            this.authoritiesPopulators = Arrays.asList(authoritiesPopulators);
        }

        @Override
        public Collection<? extends GrantedAuthority> getGrantedAuthorities(DirContextOperations userData, String username) {
            List<GrantedAuthority> grantedAuthorities = authoritiesPopulators.stream()
                    .map(authPopulator -> authPopulator.getGrantedAuthorities(userData, username))
                    .flatMap(Collection::stream)
                    .collect(Collectors.toList());
            return grantedAuthorities;
        }
    }

    /**
     * Creates context source object instead of configuring it with AuthenticationBuilder
     * @return Context source object used for accessing ldap server
     */
    @Bean
    public LdapContextSource contextSource() {
        LdapContextSource contextSource= new LdapContextSource();
        contextSource.setUrl(ldapUrl);
        contextSource.setUserDn(ldapManagerDn);
        contextSource.setPassword(ldapManagerPassword);
        contextSource.afterPropertiesSet();
        return contextSource;
    }

Session Configuration:

/**
 * This is essential to make sure that the Spring Security session registry is notified when the session is destroyed.
 * @return
 */
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
    return new HttpSessionEventPublisher();
}

The Spring Application tells me that my service account got successful connected to LDAP server.

DEBUG 17220 --- [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: org.springframework.security.authentication.AnonymousAuthenticationToken@c1e15be1: Principal: anonymousUser; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@380f4: RemoteIpAddress: 127.0.0.1; SessionId: 0A82CE8FA4FB9EB248D756EEE8134CAE; Granted Authorities: ROLE_ANONYMOUS

The error then is thrown when the found user is beeing tried to bind:

DEBUG 17220 --- [nio-8080-exec-8] o.s.s.l.a.BindAuthenticator              : Failed to bind as CN=familyName\, name,OU=Group: org.springframework.ldap.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090453, comment: AcceptSecurityContext error, data 52e, v3839 ]; nested exception is javax.naming.AuthenticationException: [LDAP: error code 49 - 80090308: LdapErr: DSID-0C090453, comment: AcceptSecurityContext error, data 52e, v3839 ]
DEBUG 17220 --- [nio-8080-exec-8] w.a.UsernamePasswordAuthenticationFilter : Authentication request failed: org.springframework.security.authentication.BadCredentialsException: Bad credentials

To sum this up: my credentials are correct for a single group base without using LdapContextSource and MultipleLdapAuthoritiesPopulator. But the authentication process seems not to provide the enteret password for my user with multiple group bases.

1

1 Answers

0
votes

After spending some time in figuring out a solution i had to admit that there was no efficient way to create a solution i.e. with overwriting methods or classes.

But i stumbled on a change request for spring security, precicely for this use case when multiple group search bases need to be checked. It is implemented since Spring Security version 5.4.1 (i believe) or included when using Spring Starter parent version 2.4.2.

Simply add the option to your authentification method:

.groupSearchSubtree(true)

The complete updated method example for authentication looks like this then:

    @Override
@Autowired
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{   
    auth
            .ldapAuthentication()
            .contextSource()
            .url(ldapUrl)
            .managerDn(ldapManagerDn)
            .managerPassword(ldapManagerPassword)
       .and()
            .userSearchFilter(ldapUserSearchFilter)
            .userSearchBase(ldapUserSearchBase)
            .groupSearchFilter(ldapGroupSearchFilter)
            .groupSearchBase(ldapGroupSearchBase)
            .groupSearchSubtree(true)
    ;

You see that there is no need for three different nodes and no more custom context object to be forwarded any more, simply add the parent node for the group search base and let the subtree search do the rest.

It might have been nice to figure out a way by myself, but using an incorporated solution of the framework is surely the better way to go.