Spring Security 3.2: @Autowire doesn't work with java configuration and custom AuthenticationProvider in Spring MVC application?

pasemes picture pasemes · Dec 24, 2013 · Viewed 10.8k times · Source

This problem is relatively well discussed in several blog posts and SO questions. Nevertheless, I wasn't able to find one specifically addressing the problem with java configuration. I'm suspecting that I'm doing something wrong in my java configuration files, since I've found some posts indicating that the problem can be resolved by removing the debug XML tag (https://jira.springsource.org/browse/SEC-1885).

I'm using 3.2.0.RELEASE of spring security, and 3.2.6.RELEASE of spring framework. Below the main files used in the spring security/mvc configuration and the custom AuthenticationProvider.

WebConfig:

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {"com.mypackage"})
@ImportResource( { "classpath:/spring-data.xml", "classpath:/trace-context.xml" })
@EnableTransactionManagement  
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
    }

    @Bean
    public StandardServletMultipartResolver multipartResolver() {
        return new StandardServletMultipartResolver();
    }

    @Bean(destroyMethod = "shutdown")
    public GraphDatabaseService graphDatabaseService() {
        return new GraphDatabaseFactory().newEmbeddedDatabase("target/temp.db");
    }

    @Bean
    public RepositoryInitializer repositoryInitializer() {
        return new RepositoryInitializer();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        LocaleChangeInterceptor localeChangeInterceptor = new         LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        registry.addInterceptor(localeChangeInterceptor);
    }

    @Bean
    public LocaleResolver localeResolver() {
        CookieLocaleResolver cookieLocaleResolver = new CookieLocaleResolver();
        cookieLocaleResolver.setDefaultLocale(StringUtils.parseLocaleString("en"));
        return cookieLocaleResolver;
    }

    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/views/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:messages/messages", "classpath:messages/validation");
        // if true, the key of the message will be displayed if the key is not
        // found, instead of throwing a NoSuchMessageException
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setDefaultEncoding("UTF-8");
        // # -1 : never reload, 0 always reload
        messageSource.setCacheSeconds(0);
        return messageSource;
    }
}

WebInitializer:

public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { WebSecurityConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        return new Filter[] { characterEncodingFilter, new SiteMeshFilter()};
    }

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        //servletContext.addListener(new HttpSessionEventPublisher());
    }
}

WebSecurityConfig:

@Configuration
@EnableWebSecurity
@Order(1)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests().anyRequest().permitAll();
        //  .antMatchers("/", "/login").permitAll()
        //  .anyRequest().authenticated();
        http
            .formLogin()
                .defaultSuccessUrl("/hello")
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .logoutUrl("/logout")
                .permitAll();
        http    
            .sessionManagement()
            .maximumSessions(1)
            .maxSessionsPreventsLogin(true);

    }    

    @Override
    public void configure(WebSecurity web) throws Exception {
        web
            .ignoring()
            .antMatchers("/resources/**");
    }

    @Override
    protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
        authManagerBuilder.authenticationProvider(new ApplicationAuthenticationProvider());
    }
}

WebSecurityInitializer:

public class WebSecurityInitializer extends AbstractSecurityWebApplicationInitializer {

}

AuthenticationProvider:

@Component(value = "authenticationProvider")
public class ApplicationAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    public UserService userService;

    public ApplicationAuthenticationProvider() {}

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();

        User user = userService.loadUserByUsername(username);

        if (user == null) {
            throw new BadCredentialsException("Username not found.");
        }

        if (!password.equals(user.getPassword())) {
            throw new BadCredentialsException("Wrong password.");
        }

        Collection<? extends GrantedAuthority> authorities = user.getAuthorities();

        return new UsernamePasswordAuthenticationToken(username, password, authorities);
    }

    @Override
    public boolean supports(Class<?> arg0) {
        return true;
    }
}

UserService:

@Service
public class UserService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;


    @Override
    public User loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}

Spring is throwing an exception while it is building its application context (during application initialization):

[ERROR] [main 11:53:37] (FrameworkServlet.java:initServletBean:467) Context     initialization failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name         'authenticationProvider': Injection of autowired dependencies failed; nested exception is     org.springframework.beans.factory.BeanCreationException: Could not autowire field: public     com.evidencefactory.service.UserService     com.evidencefactory.security.ApplicationAuthenticationProvider.userService; nested     exception is java.lang.IllegalArgumentException: Can not set     com.evidencefactory.service.UserService field     com.evidencefactory.security.ApplicationAuthenticationProvider.userService to     sun.proxy.$Proxy71

I don't understand why it is happening, but if I remove the UserDetailsService interface implementation from UserService class, then the application starts successfully. However, when ApplicationAuthenticationProvider is invoked by Spring, the UserService is not autowired into it and the application throws a NullPointerException.

java.lang.NullPointerException
at com.evidencefactory.security.ApplicationAuthenticationProvider.authenticate(ApplicationAuthenticationProvider.java:33)

Answer

pasemes picture pasemes · Dec 24, 2013

Figured out how to put it to work, although there still some issues unanswered.

1) I still don't know why Spring context initialization fails when UserService implements UserDetailsService. Given that I'm not seeing use for it, since I'm using a custom AuthenticationProvider, I just removed this implementation and things are ok for now. To the best of my knowledge (from what I could understand from my first initial reading of Spring Security reference documentation) providing a custom AuthenticationProvider or an UserDetailsService implementation are exclusive alternatives.

2) As noticed by one of the respondents (@Sotirios Delimanolis) I was instantiating ApplicatinoAuthenticationProvider by hand and since it wasn't being managed by Spring this instance would not have an UserService instance autowired into it. Based on this, I changed WebSecurityConfig to get an autowired instance of ApplicationAuthenticationProvider as can be seen below:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ApplicationAuthenticationProvider authenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
        authManagerBuilder.authenticationProvider(authenticationProvider);
    }
}

This wasn't still sufficient, because ApplicationAuthenticationProvider wasn't being autowired into WebSecurityConfig. Based on this link Spring Security 3.1.3 @Autowired not Work when using WebApplicationInitializer I noticed that this was because security config should have a component scan declaration too. Adding @ComponentScan(basePackages = {"com.mypackage"}) to WebSecurityConfig resolved the problem.