javaspring-securitydependency-injectionspring-bootuserdetailsservice

Customizing UserDetails using a service without LoadTimeWeaving


I need to use a list of objects for access control. The query to get that list is quite complex and lengthy, so I would like to cache it in the user object when a user authenticates with the server. I decided to try and solve this with a customised implementation of UserDetailService and this WebSecurityConfig:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

...


        @Override
        public void init(AuthenticationManagerBuilder auth) throws Exception {
            String ldapURI = "ldap://" + ldaHost + ":" + ldapPort + "/" + ldapBasis;

            DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapURI);
            contextSource.setUserDn(ldapUser);
            contextSource.setPassword(ldapPassword);
            contextSource.afterPropertiesSet();

            LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthenticationProviderConfigurer = auth
                    .ldapAuthentication().userDetailsContextMapper(new LdapUserDetailsContextMapper());

            ldapAuthenticationProviderConfigurer
                    .userSearchFilter("(&(criteria={0}))")
                    .userSearchBase("ou=usersearchbase").contextSource(contextSource);
        }
}

The ContextMapper to fill the user object with data:

public class LdapUserDetailsContextMapper implements UserDetailsContextMapper {

private final org.slf4j.Logger log = LoggerFactory.getLogger(this.getClass());

private DataService dataService;

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

    AppUser user = new AppUser();
    user.setUsername(ctx.getStringAttribute("uid"));
    user.setName(ctx.getStringAttribute("cn"));
    user.setSurname(ctx.getStringAttribute("sn"));
    user.setMail(ctx.getStringAttribute("mail"));
    user.setRoleUser();    

user.setManaged(dataService.getManagedData(user.getMail()));

                    return user;
                }

The problem is that I see no way to inject my DataService into this process - neither the ContextMapper nor the @Configure class are managed beans which makes @Autowired impossible. I want to avoid LoadTimeWeaving because using this would make deployments very difficult - do I have another choice?

I found these two similar problems: Why is my Spring @Autowired field null? has the same problem in a Controller class, which can be turned into a managed bean; both other mentioned solutions didn't work for me (dataService always was null) and Spring Boot, @Autowire into an unmanaged class using @Configurable and load time weaving again recommends LoadTimeWeaving.

I also found apparent solutions mentioning using @Autowired at the init method, but I couldn't get get that to work either (trying to call the ContextMapper-constructor with an injected dataService)


Solution

  • Just make it a spring managed bean.

    @Configuration
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    ...
    
    
            @Override
            public void init(AuthenticationManagerBuilder auth) throws Exception {
                String ldapURI = "ldap://" + ldaHost + ":" + ldapPort + "/" + ldapBasis;
    
                DefaultSpringSecurityContextSource contextSource = new DefaultSpringSecurityContextSource(ldapURI);
                contextSource.setUserDn(ldapUser);
                contextSource.setPassword(ldapPassword);
                contextSource.afterPropertiesSet();
    
                LdapAuthenticationProviderConfigurer<AuthenticationManagerBuilder> ldapAuthenticationProviderConfigurer = auth
                        .ldapAuthentication().userDetailsContextMapper(userDetailsContextMapper());
    
                ldapAuthenticationProviderConfigurer
                        .userSearchFilter("(&(criteria={0}))")
                        .userSearchBase("ou=usersearchbase").contextSource(contextSource);
            }
        @Bean
        public LdapUserDetailsContextMapper userDetailsContextMapper() {
            return new LdapUserDetailsContextMapper();
        }
    }
    

    And annotate your field inside the LdapUserDetailsContextMapper with @Autowired.