6

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)
pasemes
  • 566
  • 1
  • 4
  • 18

2 Answers2

5

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.

Community
  • 1
  • 1
pasemes
  • 566
  • 1
  • 4
  • 18
3

I'm going to assume that UserService is a class and has some @Transactional annotation either on itself or one of its methods.

You'll need to add CGLIB to your classpath and change your @EnableTransactionManagement to

@EnableTransactionManagement(proxyTargetClass = true)

so that Spring uses CGLIB proxying (which can proxy classes) instead of JKD proxies (which cannot).


Alternatively, you can create an interface UserService and implement (and annotate with @Service) a UserServiceImpl class. Your autowired UserService field would remain the same, but Spring will be able to use JDK proxies.

Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • Added the @EnableTransactionManagement(proxyTargetClass = true) annotation to the UserService class. The error has changed to org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'evidenceEditorController': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.evidencefactory.service.TermService com.evidencefactory.controller.EvidenceEditorController.termService; nested exception is org.springframework.beans.factory.BeanCreationException: ... – pasemes Dec 24 '13 at 02:35
  • @pasemes The annotation goes on `WebConfig`, not `UserService`. – Sotirios Delimanolis Dec 24 '13 at 02:36
  • This error does not occur if I do not use the Spring Security java configuration, i.e., without the WebSecurityConfig and WebSecurityInitializer config classes. I'm not understanding this behavior. – pasemes Dec 24 '13 at 02:38
  • @pasemes I can't find the exact cause right now, but the reason is that some bean is causing Spring to proxy your beans. It uses JDK proxies which cannot proxy classes. It's not the transaction management proxies finally. – Sotirios Delimanolis Dec 24 '13 at 02:41
  • Sorry for the lack of attention. Added the @EnableTransactionManagement(proxyTargetClass = true) annotation to both configuration classes WebConfig and WebSecurityConfig. If I understood it correlctly, the error now is indicating that spring is not able to @Autowire private members. Is that the desired behavior? – pasemes Dec 24 '13 at 02:43
  • @pasemes No, that doesn't make sense to me. Only `WebConfig` should have the annotation. – Sotirios Delimanolis Dec 24 '13 at 02:44
  • Within XML configuration, there are some posts that suggests that I should add tag (http://stackoverflow.com/questions/7023972/autowire-is-not-working-in-spring-security-custom-authentication-provider). To my understanding, the tag is already present in WebConfig class with the @ComponentScan(basePackages = {"com.evidencefactory"}) annotation. Adding the annotation to the WebSecurityConfig class does not help. – pasemes Dec 24 '13 at 02:46
  • @pasemes In your `configureGlobal` method, you are creating a new `ApplicationAuthenticationProvider` instance instead of getting a Spring managed bean. You shouldn't do this. Instead add a `@ComponentScan` annotation to `WebSecurityConfig` pointing to the package containing `ApplicationAuthenticationProvider`, inject that instance into the `WebSecurityConfig` class and use that instance instead of `new ApplicationAuthenticationProvider()`. – Sotirios Delimanolis Dec 24 '13 at 02:49
  • @pasemes There's a lot of stuff here that can potentially go wrong. Try to figure out what each annotation is meant for and then use it. There are many related questions and answers and the documentation is a must read. – Sotirios Delimanolis Dec 24 '13 at 02:50
  • I see. The configureGlobal was one of my last desperate try to fix the problem (I have updated my question). The new ApplicationAuthenticationProvider() in configure method was well noticed. Using "new" will not indeed work with @Autowire. The question is, thus, how to configure a custom Authentication Provider in Spring Security using java configuration. Adding, WebSecurityConfig and WebSecurityInitializer are the first steps, but I'm not able to figure out how to provide the custom AuthenticationProvider to Spring Security. Anyway, thanks for your attention. – pasemes Dec 24 '13 at 03:12