tutorial - Spring Security 3.2: @Autowire no funciona con la configuración de Java y AuthenticationProvider personalizado en la aplicación Spring MVC?
userdetailsservice spring security 4 (2)
Voy a suponer que UserService
es una clase y tiene alguna anotación @Transactional
en sí misma o en uno de sus métodos.
Deberá agregar CGLIB a su classpath y cambiar su @EnableTransactionManagement
a
@EnableTransactionManagement(proxyTargetClass = true)
de modo que Spring utiliza el proxy CGLIB (que puede usar clases proxy) en lugar de los proxies JKD (que no pueden).
Alternativamente, puede crear una interfaz UserService
e implementar (y anotar con @Service
) una clase UserServiceImpl
. Su campo UserService UserService
se mantendrá igual, pero Spring podrá usar proxies JDK.
Este problema se discute relativamente bien en varias publicaciones de blog y en preguntas de SO. Sin embargo, no pude encontrar uno que aborda específicamente el problema con la configuración de Java. Sospecho que estoy haciendo algo mal en mis archivos de configuración de Java, ya que he encontrado algunas publicaciones que indican que el problema se puede resolver eliminando la etiqueta XML de depuración ( https://jira.springsource.org/browse/ SEC-1885 ).
Estoy usando 3.2.0.RELEASE de seguridad de primavera, y 3.2.6.RELEASE de spring framework. Debajo de los archivos principales utilizados en la configuración spring security / mvc y el AuthenticationProvider personalizado.
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 está lanzando una excepción mientras construye su contexto de aplicación (durante la inicialización de la aplicación):
[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
No entiendo por qué está sucediendo, pero si UserDetailsService
implementación de la interfaz UserService
clase UserService
, la aplicación se iniciará correctamente. Sin embargo, cuando ApplicationAuthenticationProvider
es invocado por Spring, el UserService
no se conecta automáticamente y la aplicación lanza una NullPointerException.
java.lang.NullPointerException
at com.evidencefactory.security.ApplicationAuthenticationProvider.authenticate(ApplicationAuthenticationProvider.java:33)
Descubrí cómo ponerlo a funcionar, aunque todavía quedan algunos problemas sin respuesta.
1) Todavía no sé por qué la inicialización del contexto Spring falla cuando UserService
implementa UserDetailsService
. Dado que no veo uso para él, ya que estoy usando un AuthenticationProvider
personalizado, acabo de eliminar esta implementación y las cosas están bien por ahora. A mi leal saber y entender (por lo que pude entender desde mi primera lectura inicial de la documentación de referencia de Spring Security), el proporcionar un AuthenticationProvider
personalizado o una implementación de UserDetailsService
son alternativas exclusivas.
2) Tal como lo notó uno de los encuestados (@Sotirios Delimanolis) estaba instanciando ApplicatinoAuthenticationProvider
a mano y como no estaba siendo administrado por Spring, esta instancia no tendría una instancia de UserService UserService
automáticamente. En base a esto, cambié WebSecurityConfig
para obtener una instancia de ApplicationAuthenticationProvider
autocableado como se puede ver a continuación:
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private ApplicationAuthenticationProvider authenticationProvider;
@Override
protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception {
authManagerBuilder.authenticationProvider(authenticationProvider);
}
}
Esto aún no era suficiente, porque ApplicationAuthenticationProvider
no se estaba autoconectando en WebSecurityConfig
. Basado en este enlace Spring Security 3.1.3 @Autowired not Work cuando se usa WebApplicationInitializer Noté que esto se debía a que la configuración de seguridad también debería tener una declaración de exploración de componentes. Agregar @ComponentScan(basePackages = {"com.mypackage"})
a WebSecurityConfig
resolvió el problema.