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)
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
toso 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
) aUserServiceImpl
class. Your autowiredUserService
field would remain the same, but Spring will be able to use JDK proxies.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
implementsUserDetailsService
. Given that I'm not seeing use for it, since I'm using a customAuthenticationProvider
, 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 customAuthenticationProvider
or anUserDetailsService
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 anUserService
instance autowired into it. Based on this, I changedWebSecurityConfig
to get an autowired instance ofApplicationAuthenticationProvider
as can be seen below:This wasn't still sufficient, because
ApplicationAuthenticationProvider
wasn't being autowired intoWebSecurityConfig
. 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"})
toWebSecurityConfig
resolved the problem.