DataSourceTransactionManager bean not created after Introducing Spring Batch #EnableBatchProcessing Annotation - spring

I am integrating Spring Batch in one of the existing applications where other beans are successfully using DataSourceTransactionManager. However, I am getting the following error when I introduce the #EnableBatchProcessing annotation :
Field transactionManager in AuthBeans required a bean of type 'org.springframework.jdbc.datasource.DataSourceTransactionManager' that could not be found.
I've tried to configure BatchConfigurer but it has not resolved the issue. Please see BatchConfigurer code below.
#Configuration
public class CustomBatchConfigurer implements BatchConfigurer {
#Autowired
private DataSource dataSource;
#Override
public JobRepository getJobRepository() throws Exception {
JobRepositoryFactoryBean factory = new JobRepositoryFactoryBean();
factory.setDataSource(dataSource);
factory.setTransactionManager(getTransactionManager());
factory.afterPropertiesSet();
return (JobRepository) factory.getObject();
}
#Override
public PlatformTransactionManager getTransactionManager() throws Exception {
return new DataSourceTransactionManager(dataSource);
}
#Override
public JobLauncher getJobLauncher() throws Exception {
SimpleJobLauncher jobLauncher = new SimpleJobLauncher();
jobLauncher.setJobRepository(getJobRepository());
jobLauncher.afterPropertiesSet();
return jobLauncher;
}
#Override
public JobExplorer getJobExplorer() throws Exception {
JobExplorerFactoryBean factory = new JobExplorerFactoryBean();
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return (JobExplorer) factory.getObject();
}
I am wondering am I missing something? Your help regarding this will be highly appreciated.

In which class , you kept #EnableBatchProcessing annotation.
#EnableBatchProcessing should be at the highest level.
#EnableBatchProcessing will create the DataSourceTransactionManager by default.
Go through this BatchConfigure and follow the example.
I hope this will resolve your issue.

Related

Spring Boot 3 BeanCreationException: Error creating bean with name authorizationEndpoint - ClassNotFoundException: javax.servlet.ServletException

I have a simple-as-possible OAuth2 JWT Spring Boot 2.7.3 project that works perfectly. It's easy to setup and run as described in the README from Github repository.
Besides Spring Security, the project uses spring-security-oauth2:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Then defines some beans:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Value("${jwt.secret}")
private String jwtSecret;
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
tokenConverter.setSigningKey(jwtSecret);
return tokenConverter;
}
#Bean
public JwtTokenStore tokenStore() {
return new JwtTokenStore(accessTokenConverter());
}
#Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
}
And an Authorization Server:
#Configuration
#EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
#Value("${security.oauth2.client.client-id}")
private String clientId;
#Value("${security.oauth2.client.client-secret}")
private String clientSecret;
#Value("${jwt.duration}")
private Integer jwtDuration;
#Autowired
private BCryptPasswordEncoder passwordEncoder;
#Autowired
private JwtAccessTokenConverter accessTokenConverter;
#Autowired
private JwtTokenStore tokenStore;
#Autowired
private AuthenticationManager authenticationManager;
#Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
security.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");
}
#Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory()
.withClient(clientId)
.secret(passwordEncoder.encode(clientSecret))
.scopes("read", "write")
.authorizedGrantTypes("password")
.accessTokenValiditySeconds(jwtDuration);
}
#Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(authenticationManager)
.tokenStore(tokenStore)
.accessTokenConverter(accessTokenConverter);
}
}
And a Resource Server:
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Value("${cors.origins}")
private String corsOrigins;
#Autowired
private Environment env;
#Autowired
private JwtTokenStore tokenStore;
private static final String[] PUBLIC = { "/oauth/token", "/h2-console/**" };
#Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.tokenStore(tokenStore);
}
#Override
public void configure(HttpSecurity http) throws Exception {
// H2
if (Arrays.asList(env.getActiveProfiles()).contains("test")) {
http.headers().frameOptions().disable();
}
http.authorizeRequests()
.antMatchers(PUBLIC).permitAll()
.anyRequest().authenticated();
http.cors().configurationSource(corsConfigurationSource());
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
String[] origins = corsOrigins.split(",");
CorsConfiguration corsConfig = new CorsConfiguration();
corsConfig.setAllowedOriginPatterns(Arrays.asList(origins));
corsConfig.setAllowedMethods(Arrays.asList("POST", "GET", "PUT", "DELETE", "PATCH"));
corsConfig.setAllowCredentials(true);
corsConfig.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfig);
return source;
}
#Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
FilterRegistrationBean<CorsFilter> bean
= new FilterRegistrationBean<>(new CorsFilter(corsConfigurationSource()));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
}
Now I'm trying to upgrade it to Spring Boot 3.0.0-SNAPSHOT, but the project is throwing BeanCreationException: Error creating bean with name 'authorizationEndpoint'. The stack trace shows it's caused by ClassNotFoundException: javax.servlet.ServletException.
How to fix that?
The problem is that you are using an incompatible version of spring cloud (Hoxton.SR8). It is only compatible with spring boot 2.3.x and 2.2.x (see https://spring.io/blog/2020/08/28/spring-cloud-hoxton-sr8-has-been-released). You need to upgrade spring cloud as well.
(Spring boot 3 uses the new Jakarta APIs so jakarta.servlet.ServletException is only available and downloaded by maven after you upgraded Spring boot. Your incompatible spring cloud version expects the old javax.servlet.ServletException class, but is not there anymore. Therefore this ClassNotFoundException.)
The dependency:
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
</dependency>
Has reached its End of Life some time ago. Therefore, it won't be compatible with Spring Boot 3. The project has been replaced by the OAuth2 support provided by Spring Security (client and resource server) and Spring Authorization Server.
When I first ran the application(converted to gradle), I got the exception java.lang.ClassNotFoundException: javax.servlet.Filter. It went away once I added the javax-servlet-api dependency. Can you also try adding the below dependency?
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>

Security configuration with spring boot

I'm a beginner, I want to code the password when adding users but there is an error (#Bean).
Error:
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Description:
Field passwordEncoder in com.example.demo.service.UsersService required a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'org.springframework.security.crypto.password.PasswordEncoder' in your configuration.
My code:
#Configuration
#EnableConfigurationProperties
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UsersService userService;
#Override
protected void configure(HttpSecurity http) throws Exception {
// TODO Auto-generated method stub
http.csrf().disable() //TODO implement csrf
.cors().and().authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/add-users").permitAll()
.and().authorizeRequests().anyRequest().authenticated();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// TODO Auto-generated method stub
auth.userDetailsService(userService)
.passwordEncoder(passwordEncoder());
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
// TODO Auto-generated method stub
return super.authenticationManagerBean();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
public class UsersService implements UserDetailsService {
#Autowired
UsersRepository repo;
#Autowired
private PasswordEncoder passwordEncoder;
public Users save(Users u) {
String encodpass=passwordEncoder.encode(u.getPassword());
String confpass=passwordEncoder.encode(u.getConfirmepass());
u.setConfirmpass(confpass);
u.setPassword(encodpass);
u.setLock(false);
u.setEnable(true);
return repo.save(u);
}
}
#RestController
public class UsersController {
#Autowired
private UsersService service;
#PostMapping("/add-users")
public Users add(#RequestBody Users u) {
return service.save(u);}
The problem is that in your SecurityConfiguration you are injecting UsersService, and the UserService bean depends on a bean of type PasswordEncoder that has not been created yet.
I'll simplify your configuration by using a more ellegant way of configuring Spring Security:
#Configuration
//your other annotations
public class SecurityConfiguration {
#Bean
public SecurityFilterChain app(HttpSecurity http) throws Exception {
http.csrf().disable().cors().and().authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/").permitAll()
.antMatchers("/login").permitAll()
.antMatchers("/add-users").permitAll()
.and().authorizeRequests().anyRequest().authenticated();
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
And that's it, you don't really need the AuthenticationManager bean unless you are actually using it. In case that you need it, you can create it like this:
#Bean
public AuthenticationManager authenticationManager(UsersService usersService) {
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(usersService);
ProviderManager manager = new ProviderManager(daoAuthenticationProvider);
return manager;
}
I apologize for any compilation error beforehand because I wrote this from a phone.
add #Service annotation to UserService class. also add #EnableWebSecurity to config class.

After implementing Spring Session Management Spring security keeps forwarding me to the login page

I am working on JEE application. We recently switched from container based security to spring security. I am now trying move session handling out of the container and into the application using spring-session-jdbc. I've created the required tables in our database and created the following SessionConfig file:
#Configuration
#EnableJdbcHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer {
#Bean
public DataSource jndiDataSource() throws IllegalArgumentException, NamingException {
JndiObjectFactoryBean bean = new JndiObjectFactoryBean();
bean.setJndiName("java:jboss/MyDS");
bean.setProxyInterface(DataSource.class);
bean.setLookupOnStartup(false);
bean.afterPropertiesSet();
return (DataSource) bean.getObject();
}
#Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
#Bean
public FindByIndexNameSessionRepository<?> sessionRepository(PlatformTransactionManager txManager,
DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
TransactionTemplate txTemplate = new TransactionTemplate(txManager);
return new JdbcIndexedSessionRepository(jdbcTemplate, txTemplate);
}
}
We have a security config where I autowire the the sessionRepository and use it to create the SessionAuthenticationStrategy like:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
public FindByIndexNameSessionRepository<?> repo;
#Override
public void configure(WebSecurity web) throws Exception {
// put all static resource or external urls here
web.ignoring().antMatchers("/external/**", "/react/**", "/images/**", "/js/**", "/css/**",
"/vendor/**", "/fonts/**");
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
String maxSessions =
GenericPropertiesReader.getInstance().getValue("config.logins.max.sessions");
http.sessionManagement()// set the session management
.sessionAuthenticationStrategy(sessionAuthenticationStrategy())
.invalidSessionUrl("/login.html") // no user session forward here
.maximumSessions(Integer.valueOf(maxSessions))// 1 or -1 for unlimited
.maxSessionsPreventsLogin(false)// new session will terminate old session and forward them
// to the log in page
.expiredUrl("/login.html?type=expired-session");
http.headers().frameOptions().disable();
http.authorizeRequests()// put any antMatchers after this
.antMatchers("/login.html").permitAll() // permit any login page
.anyRequest().authenticated().and().formLogin() // we are using form login
.loginPage("/login.html") // show our custom login form
.loginProcessingUrl("/login") // post to Spring's action URL so our custom auth provider is invoked
.successHandler(successHandler()).failureHandler(failureHandler())
.permitAll() // so anyone can see it
.and().logout().logoutUrl("/logout")
.logoutSuccessHandler(new MyLogoutSuccessHandler())// our custom logout handler
.invalidateHttpSession(true) // delete session, need more work??
.deleteCookies("JSESSIONID") // and get rid of that cookie so they can't auto-login again
.permitAll()
.and().x509().x509AuthenticationFilter(this.x509AuthFilter());
}
#Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(x509AuthProvider()).authenticationProvider(loginAuthProvider());
}
#Bean
public PreAuthenticatedAuthenticationProvider x509AuthProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(x509PreAuthUserDetailsService());
return provider;
}
#Bean // this irks me.
public AuthenticationManager myAuthenticationManager() throws Exception {
return this.authenticationManager();
}
#Bean
X509AuthenticationFilter x509AuthFilter() throws Exception {
X509AuthenticationFilter filter = new X509AuthenticationFilter();
filter.setAuthenticationSuccessHandler(x509SuccessHandler());
filter.setPrincipalExtractor(x509Extractor());
filter.setAuthenticationManager(this.myAuthenticationManager());
filter.setAuthenticationFailureHandler(failureHandler());
return filter;
}
#Bean
public X509PrincipalExtractor x509Extractor() {
return new MyX509DodIdExtractor();
}
#Bean
public MyX509PreAuthUserDetailsService x509PreAuthUserDetailsService() {
return new MyX509PreAuthUserDetailsService();
}
#Bean
public MyAuthenticationProvider loginAuthProvider() {
return new MyAuthenticationProvider ();
}
#Bean
MyAuthenticationSuccessHandler x509SuccessHandler() {
MyAuthenticationSuccessHandler handler = new MyAuthenticationSuccessHandler ();
handler.setForwardResonse(false);
return handler;
}
#Bean
public MyAuthenticationSuccessHandler successHandler() {
return new MyAuthenticationSuccessHandler();
}
#Bean
public MyAuthenticationFailureHandler failureHandler() {
MyAuthenticationFailureHandler failureHandler = new MyAuthenticationFailureHandler();
failureHandler.setExceptionMappings(LoginFailures.exceptionMap());
failureHandler.setDefaultFailureUrl("/login.html?login-failure=" + LoginFailures.DEFAULT.code);
return failureHandler;
}
#Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
#Bean
public SpringSessionBackedSessionRegistry<? extends Session> sessionRegistry()
throws IllegalArgumentException, NamingException {
return new SpringSessionBackedSessionRegistry<>(repo);
}
#Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategy()
throws IllegalArgumentException, NamingException {
ConcurrentSessionControlAuthenticationStrategy sessionAuthenticationStrategy =
new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());
return sessionAuthenticationStrategy;
}
}
I see the session tables in the database being filled when attempting to login. I never hit any of the authentication code when debugging. I just am redirected to the login page every time.
I feel like I must be missing something obvious. I was getting errors that there was no unique bean of type FindByIndexNameSessionRepository<?> until I changed the name of the bean in SessionConfig to sessionRepository. Which makes me think there's another bean of that type being instantiated by Spring (not in our code base) that might be interfering?

Error creating and injecting BCryptPasswordEncoder in a service class

I want to hash and store the password in the database. When I try to inject the PasswordEncoder bean into my service class, I get the error mentioned below.
This is my first project using the Spring-security module and would appreciate your advice.
Spring Boot version:2.2.6.RELEASE
**SecurityConfiguration.java: security configuration class*
#EnableWebSecurity
#Configuration
#ComponentScan(basePackages = { "com.sd.authandauthorizationbackendapp.security" })
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
UserDetailsService userDetailsService;
#Override
#Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
auth.parentAuthenticationManager(authenticationManagerBean())
.userDetailsService(userDetailsService).
.and().authenticationProvider(authProvider());
}
#Bean
public DaoAuthenticationProvider authProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().csrf().disable().authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.antMatchers("/test").hasRole("USER")
.antMatchers("/register").permitAll()
.antMatchers("/").permitAll()
.and().formLogin();
}
#Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder(10, new SecureRandom());
}
}
UserServiceImpl.java: a service class to save user
#Service
public class UserServiceImpl implements UserService {
#Autowired
private UserRepository userRepository;
#Autowired
PasswordEncoder bCryptPasswordEncoder;
#Override
public void save(User user) {
user.setPassword(bCryptPasswordEncoder.
encode(user.getPassword()));
user.setRoles(user.getRoles());
userRepository.save(user);
}
}
ERROR
Unsatisfied dependency expressed through field 'bCryptPasswordEncoder'; Error creating
bean with name 'passwordEncoder': Requested bean is currently in creation: Is there an
unresolvable circular reference?
Please let me know if further code and details are needed.
Unless you use #Qualified("passwordEncoder") in the service class then spring will look for bCryptPasswordEncoder as bean. At the moment you are looking for a bean called bCryptPasswordEncoder.
Change it to
#Autowired
PasswordEncoder passwordEncoder;
or
#Qualified("passwordEncoder")
#Autowired
PasswordEncoder bCryptPasswordEncoder;
By removing #Autowired from PasswordEncoder the problem will be going to resolve.
As you’re creating the PasswordEncoder in the same class that you’re injecting it.

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

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)
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.
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.

Resources