Spring Boot. A Bean Depends on a Service - java

Below I have a customUserDetailsService property, and a tokenAuthenticationService property. I need to pass customUserDetailsService into tokenAuthenticationService but tokenAuthenticationService is a #Bean file and customUserDetailsService is a #Service which means that tokenAuthenticationService gets called first with parameter for UserDetailsService as null for the UserDetailsService parameter. I need to either delay the initiation of tokenAuthenticationService as a Bean or Turn tokenAuthenticationService into a service as well and some how pass those parameters as a constructor. How do I go about doing this ?
package app.config;
import app.repo.User.CustomUserDetailsService;
import app.security.*;
import app.security.filters.StatelessAuthenticationFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.sql.DataSource;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true)
#Order(2)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private static PasswordEncoder encoder;
#Autowired
private TokenAuthenticationService tokenAuthenticationService;
#Autowired
private UserDetailsService customUserDetailsService;
#Autowired
private RESTAuthenticationEntryPoint authenticationEntryPoint;
#Autowired
private RESTAuthenticationFailureHandler authenticationFailureHandler;
#Autowired
private RESTAuthenticationSuccessHandler authenticationSuccessHandler;
public WebSecurityConfig() {
super(true);
}
#Autowired
public void configureAuth(AuthenticationManagerBuilder auth,DataSource dataSource) throws Exception {
auth.jdbcAuthentication().dataSource(dataSource);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").authenticated();
http.csrf().disable();
http.httpBasic();
http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint);
http.formLogin().defaultSuccessUrl("/").successHandler(authenticationSuccessHandler);
http.formLogin().failureHandler(authenticationFailureHandler);
http.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService),
UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(customUserDetailsService);
}
#Bean
public TokenAuthenticationService tokenAuthenticationService() {
tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", customUserDetailsService);
return tokenAuthenticationService;
}
}

You can define the userDetailsService as direct dependency of the TokenAuthenticationService like this:
#Bean
public TokenAuthenticationService tokenAuthenticationService(UserDetailsService userDetailsService) {
tokenAuthenticationService = new TokenAuthenticationService("tooManySecrets", userDetailsService);
return tokenAuthenticationService;
}
That way, Spring will make sure, that the UserDetailsService is instantiated and injected when the TokenAuthenticationService is created.

You can try annotating tokenAuthenticationService() with #Lazy. Though even if that worked its a bit unpredictable, and future modifications to this or related beans may leave you wondering on why it stopped working.
Best to declare TokenAuthenticationService as #Service & inject UserDetailsService in it.
As a side note its better to not mix #Configuration with application code to avoid these kind of issues.
Update - I don't think #Lazy is going to work here. Since you are relying on #Bean being invoked in the middle of #Autowired beans being processed.
In order for your code to work the #Autowired customUserDetailsService should be set first, then #Bean method called and then #Autowired tokenAuthenticationService should be set.

Related

Not able to autowire fields of WebSecurityConfigurerAdapter

This is my implementation of WebSecurityConfigurerAdapter:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JWTUserDetailsService jwtUserDetailsService;
#Autowired
private JWTRequestFilter jwtRequestFilter;
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint() {
return new JWTAuthenticationEntryPoint();
}
#Bean
public JWTUserDetailsService jwtUserDetailsService() {
return new JWTUserDetailsService();
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
Implementation omitted
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
implementation omitted
}
}
This is my implementation of User Detail Service:
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import tidbit.models.Reader;
import tidbit.models.ReaderRepository;
#Component
public class JWTUserDetailsService implements UserDetailsService {
#Autowired private ReaderRepository<Reader> repository;
public JWTUserDetailsService() {
super();
}
#Override
public UserDetails loadUserByUsername(String username) {
Implementation omitted
}
}
This is my implementation of the Request Filter
import io.jsonwebtoken.ExpiredJwtException;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
#Component
public class JWTRequestFilter extends OncePerRequestFilter {
private final JWTUserDetailsService jwtUserDetailsService;
private final JWTTokenUtils jwtTokenUtils;
#Autowired
public JWTRequestFilter(
JWTUserDetailsService jwtUserDetailsService, JWTTokenUtils jwtTokenUtils) {
this.jwtUserDetailsService = jwtUserDetailsService;
this.jwtTokenUtils = jwtTokenUtils;
}
#Override
protected void doFilterInternal(
Implementation omitted
}
}
In the WebSecurityConfig class I get an error for all the fields saying that Spring cannot autowire those fields:
Could not autowire. No beans of 'JWTRequestFilter' found.
I was able to solve this problem for the first two fields by creating those two methods annotated with #Bean.
However, I don't know how solve it for RequestFilter since it contains a constructor with parameters that technically should be already autowired.
In general, I think that I shouldn't have this error in the first place. I have looked at some tutorials online and they were able to autowire those fields without any problems and without creating any #bean method.
EDIT:
Thank you for your help:
Now I get the following error
Parameter 3 of constructor in tidbit.controllers.AuthenticationController required a single bean, but 2 were found:
- JWTUserDetailsService: defined in file [/Users/matteoomenetti/Documents/TidBit/backend/build/classes/java/main/tidbit/config/JWTUserDetailsService.class]
- jwtUserDetailsService: defined by method 'jwtUserDetailsService' in class path resource [tidbit/config/WebSecurityConfig.class]
It looks like I'm creating two beans of jwtUserDetailService. One is already defined in jwtUserDetailService and the other one is defined in WebSecurityConfig with the #bean annotation. However, if I already have a bean in jwtUserDetailService why isn't WebSecurityConfig finding it?
I have noticed that if I try to autowire jwtUserDetailService in any other class (without the bean method that I created) it works perfectly, meaning that the bean is found. For some reason my WebSecurityConfig class doesn't find beans...
SOLUTION
The error that I get is an error only of IntelliJ. If I try to compile through the terminal I don't get any error.
Thank you everybody
I suggest you stop using new keyword to instantiate beans. modify the following methods:
#Bean
public JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint() {
return jwtAuthenticationEntryPoint;
}
#Bean
public JWTUserDetailsService jwtUserDetailsService() {
return jwtUserDetailsService;
}
Also, modify the following class as follows:
#Component
public class JWTRequestFilter extends OncePerRequestFilter {
#Autowired
private final JWTUserDetailsService jwtUserDetailsService;
#Autowired
private final JWTTokenUtils jwtTokenUtils;
public JWTRequestFilter() {
}
#Override
protected void doFilterInternal(
Implementation omitted
}
}
You should not create new #Bean except the for the AuthenticationManager and PasswordEncoder.
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
#Autowired
private JWTUserDetailsService jwtUserDetailsService;
#Autowired
private JWTRequestFilter jwtRequestFilter;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider());
}
#Override
protected void configure(HttpSecurity http) throws Exception {
//authorizeRequests() order is important
http
.csrf().disable()
.authorizeRequests().antMatchers("/**").authenticated()
.and()
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint);
}
#Override
#Bean(name = BeanIds.AUTHENTICATION_MANAGER)
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
#Bean
public DaoAuthenticationProvider authenticationProvider(){
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(jwtUserDetailsService);
provider.setPasswordEncoder(encoder());
return provider;
}
#Bean
public PasswordEncoder encoder(){
return new BCryptPasswordEncoder(12);
}
}
Note: you can use the DaoAuthenticationProvider omit it by adding the jwtUserDetailsService in:
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
}

Spring Boot - Test Rest Api With Custom PreAuthorize Expression and Authentication

I looking for a way how to test Spring Boot REST API with following setup:
#RestController
class SomeRestController {
#Autowired
private SomeService someService;
#GetMapping("/getSome")
#PreAuthorize("#canGetSome.isValid()")
public SomeObject getSomeObject() {
return someService.getSomeObject();
}
}
_
#Component
public class CanGetSome{
#Autowired
private final LoggedUser loggedUser;
public boolean isValid() {
return loggedUser.getPermissions().isCanGetSome();
}
}
_
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
...
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.sessionFixation().newSession()
.and()
.authorizeRequests())
.anyRequest().authenticated();
}
//custom LoggedUser object which is initzialized on authentication sucessfull
#Bean
#Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public LoggedUser loggedUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return (LoggedUser) authentication.getPrincipal();
}
...
}
My test case:
#SpringBootTest(
classes = SpringBootApplication,
webEnvironment = RANDOM_PORT)
#ContextConfiguration
class RestSecurityTest extends Specification {
#Autowired
private TestRestTemplate testRestTemplate
#LocalServerPort
private int port
#WithCustomMockUser
def "test testRestTemplare"(){
expect:
def respone = testRestTemplate.getForObject('http://localhost:'+ port+'/getSome', String)
}
_
public class WithCustomMockUserSecurityContextFactory implements WithSecurityContextFactory<WithCustomMockUser> {
#Override
public SecurityContext createSecurityContext(WithCustomMockUser annotation) {
//init securityContext like #WithMockUser but with LoggedUser as principal which return true on loggedUser.getPermissions().isCanGetSome();
}
}
Unfortunately I getting following response in test:
{
"timestamp": 1524759173851,
"status": 403,
"error": "Forbidden",
"message": "Access Denied",
"path": "/getSome"
}
I also debug different spring filters after request and there SecurityContext authentication is null and later is switched to AnonymousAuthenticationToken
I don't know why SecurityContext is a null after request and isn't SecurityContext which is initialized with #WithCustomMockUser annotation. Any ideas how to fix it ?
Spring MVC should be the way to go.
Test class:-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { TestContext.class, TestWebMvcConfigurerAdapter.class, SecurityConfiguration .class })
#WebAppConfiguration
public class SomeControllerTest {
private MockMvc mockMvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Autowired
private FilterChainProxy springSecurityFilterChain;
#Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.addFilters(this.springSecurityFilterChain)
.build();
}
#Test
public void shouldPreAuthorise() throws Exception {
this.mockMvc.perform(get("/getSome")
.with(user("user.name"))
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON))
.andExpect(status().isOk());
}
}
Test web configurer:-
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#Configuration
#EnableWebMvc
#ComponentScan(basePackages = {
"your.package.here"
})
public class TestWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
}
Some mock objects:-
#Configuration
public class TestContext {
#Bean
public SomeService someService() {
return mock(SomeService.class);
}
}

Java - Spring - Basic authentication - How to hash the password defined in application.properties

Iam implementing a basic authentication for Spring Boot application and iam defining my credentials in application.properties class but I want to hash-encode the password and then check if the hash is the same as the hash for the password in application.properties then I can login. If possible to do all of the logic in the configure method then it would be great.
application.properties:
BASIC AUTHENTICATION
user.name=test
user.password={noop}example
SecurityConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
private AuthenticationProvider authenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and().sessionManagement().and().authenticationProvider(authenticationProvider)
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
UPDATED CODE
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Value("${security.user.password}")
private String password;
#Value("${security.user.name}")
private String username;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().authenticated()
.and().logout().and().httpBasic().and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().
passwordEncoder(passwordEncoder()).withUser(username).password(password);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
public String generateHashedPassword(String password) {
return BCrypt.hashpw(password, BCrypt.gensalt(10));
}
}
UPDATE 2
Currently the way it works now is when i start the application, i visit localhost:8080 then a login popup appears and i type the username and password (that are defined in application.properties)
if I type the right username and password i get logged in but if i manage to login with the username and password defined in application.properties then whats the point with hashing the password? I was thinking more like having a list of hashed keys and compare the input password with the list and if success then login.
Since you want to define your credentials in properties file, I guess you can take advantage of inmemory authentication. Try the following:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
private AuthenticationProvider authenticationProvider;
#Value("${user.name}")
private String userName;
#Value("${user.password}")
private String userHashedPassword; // hashed password
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic()
.and().sessionManagement().and().authenticationProvider(authenticationProvider)
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth
.inMemoryAuthentication()
.passwordEncoder(passwordEncoder())
.withUser(userName)
.password(userHashedPassword);
}
#Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Please, note, that in this case your password should be encrypted with BCryptPasswordEncoder first, and then you should put it into properties file (you can use its encoder.encode("password") method). Or you can use any other implementation of PasswordEncoder if you want. I've also noticed that you're using some custom autenticationProvider. Not sure how it works since you didnt share the code, and not sure that it will get along with inmemory autentication. But, anyway, I think it worth a shot and this is the right way to go in your scenario.
Hope it helps.
I think you need to implement your own AuthenticationProvider like in this question. In the authenticate() method you can do the hashing of the retrieved password and check if it matches the one from your application.properties.

Spring boot #Qualifier doesn't work with datasources

I'm building JPA configuration with multiple persistence units using different in-memory datasources, but the configuration fails resolving the qualified datasource for entity manager factory bean with the following error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of method emfb in datasources.Application$PersistenceConfiguration required a single bean, but 2 were found:
- ds1: defined by method 'ds1' in class path resource [datasources/Application$PersistenceConfiguration.class]
- ds2: defined by method 'ds2' in class path resource [datasources/Application$PersistenceConfiguration.class]
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
Here is the sample application
package datasources;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.sql.DataSource;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import org.apache.log4j.Logger;
import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.stereotype.Component;
#Configuration
#EnableAutoConfiguration(exclude = {
// HibernateJpaAutoConfiguration.class,
// DataSourceAutoConfiguration.class
JtaAutoConfiguration.class
})
#ComponentScan
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.build()
.run(args);
}
#Component
#Path("/ds")
public static class DsApi {
private final static Logger logger = Logger.getLogger(DsApi.class);
#Autowired(required = false)
#Qualifier("ds1")
private DataSource ds;
#GET
public String ds() {
logger.info("ds");
return ds.toString();
}
}
#Component
#Path("/em")
public static class EmApi {
private final static Logger logger = Logger.getLogger(EmApi.class);
#PersistenceContext(unitName = "ds2", type = PersistenceContextType.TRANSACTION)
private EntityManager em;
#GET
public String em() {
logger.info("em");
return em.toString();
}
}
#Configuration
#ApplicationPath("/jersey")
public static class JerseyConfig extends ResourceConfig {
public JerseyConfig() {
register(DsApi.class);
register(EmApi.class);
}
}
#Configuration
public static class PersistenceConfiguration {
#Bean
#Qualifier("ds1")
public DataSource ds1() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
#Qualifier("ds2")
public DataSource ds2() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
#Primary
#Autowired
public LocalContainerEntityManagerFactoryBean emfb(#Qualifier("ds1") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(Application.class)
.persistenceUnit("ds1")
.build();
}
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean emfb2(#Qualifier("ds2") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(Application.class)
.persistenceUnit("ds2")
.build();
}
}
}
The error is indicating that at some point in the application, a bean is being injected by the type DataSource and not being qualified by name at that point.
It does not matter that you have added #Qualifier in one location. The injection is failing in some other location that has not been qualified. It's not your fault though because that location is in Spring Boot's DataSourceAutoConfiguration which you should be able to see in your stack trace, below the piece that you have posted.
I would recommend excluding DataSourceAutoConfiguration i.e. #SpringBootApplication(exclude = DataSourceAutoConfiguration.class). Otherwise, this configuration is only being applied to the bean you have made #Primary. Unless you know exactly what that is, it is likely to result in subtle and unexpected differences in behaviour between your DataSources.
Declare one of your DataSource as #Primary.
Also you have 2 beans of same type - LocalContainerEntityManagerFactoryBean, declare one of them #Primary as well, as follows:
#Configuration
public static class PersistenceConfiguration {
#Bean
#Primary
public DataSource ds1() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
public DataSource ds2() {
return new EmbeddedDatabaseBuilder().build();
}
#Bean
#Primary
#Autowired
public LocalContainerEntityManagerFactoryBean emfb(#Qualifier("ds1") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(DemoApplication.class)
.persistenceUnit("ds1")
.build();
}
#Bean
#Autowired
public LocalContainerEntityManagerFactoryBean emfb2(#Qualifier("ds2") DataSource ds, EntityManagerFactoryBuilder emfb) {
return emfb.dataSource(ds)
.packages(DemoApplication.class)
.persistenceUnit("ds2")
.build();
}
}
Try declaring the datasource beans outside the static class . I.e directly in Application.java

Spring 4 + Hibernate 4 IllegalArgumentException: Property 'sessionFactory' is required

I'm writing a DAL with Spring Data and Hibernate but I'm running into a IllegalArgumentException exception which is stopping my work.
Here is the DALConf.java class which contains DataSource and persistence exception translation processor configurations
package my.dal.config;
import java.util.Properties;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.apache.commons.dbcp2.BasicDataSource;
import org.apache.commons.dbcp2.BasicDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
#Configuration
#ComponentScan(basePackages = { "my.dal" })
#PropertySource("classpath:dbconnection.properties")
public class DALConfig {
private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
private static final String PROPERTY_NAME_DATABASE_PASSWORD = "db.password";
private static final String PROPERTY_NAME_DATABASE_URL = "db.url";
private static final String PROPERTY_NAME_DATABASE_USERNAME = "db.username";
private static final String PROPERTY_NAME_POOL_INITIAL_SIZE = "pool.initialsize";
private static final String PROPERTY_NAME_POOL_MAX_IDLE = "pool.maxidle";
#Resource
private Environment environment;
#Bean
public DataSource dataSource() throws Exception
{
Properties props = new Properties();
props.put("driverClassName", environment.getProperty(PROPERTY_NAME_DATABASE_DRIVER));
props.put("url", environment.getProperty(PROPERTY_NAME_DATABASE_URL));
props.put("username", environment.getProperty(PROPERTY_NAME_DATABASE_USERNAME));
props.put("password", environment.getProperty(PROPERTY_NAME_DATABASE_PASSWORD));
props.put("initialSize", environment.getProperty(PROPERTY_NAME_POOL_INITIAL_SIZE));
props.put("maxIdle", environment.getProperty(PROPERTY_NAME_POOL_MAX_IDLE));
BasicDataSource bds = BasicDataSourceFactory.createDataSource(props);
return bds;
}
#Bean
public PersistenceExceptionTranslationPostProcessor persistenceExceptionTranslationPostProcessor()
{
PersistenceExceptionTranslationPostProcessor b = new PersistenceExceptionTranslationPostProcessor();
return b;
}
}
Then here is the HibernateConfig.class which contains Hibernate configurations
package my.dal.config;
import java.util.Properties;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.orm.hibernate4.HibernateExceptionTranslator;
import org.springframework.orm.hibernate4.HibernateTransactionManager;
import org.springframework.orm.hibernate4.LocalSessionFactoryBean;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#Configuration
#ComponentScan(basePackages = { "my.dal" })
#PropertySource("classpath:hibernate.properties")
#EnableTransactionManagement
public class HibernateConfig {
private static final String PROPERTY_NAME_DAL_CLASSES_PACKAGE = "hibernate.dal.package";
private static final String PROPERTY_NAME_HIBERNATE_DIALECT = "hibernate.dialect";
private static final String PROPERTY_NAME_HIBERNATE_SHOW_SQL = "hibernate.showsql";
#Resource
private Environment environment;
#Autowired
DataSource dataSource;
#Bean
public SessionFactory sessionFactory()
{
LocalSessionFactoryBean lsfb = new LocalSessionFactoryBean();
lsfb.setPackagesToScan(environment.getProperty(PROPERTY_NAME_DAL_CLASSES_PACKAGE));
Properties hibernateProperties = new Properties();
hibernateProperties.put("dialect", environment.getProperty(PROPERTY_NAME_HIBERNATE_DIALECT));
hibernateProperties.put("show_sql", environment.getProperty(PROPERTY_NAME_HIBERNATE_SHOW_SQL));
lsfb.setHibernateProperties(hibernateProperties);
lsfb.setDataSource(dataSource);
return lsfb.getObject();
}
#Bean
public HibernateExceptionTranslator hibernateExceptionTranslator(){
return new HibernateExceptionTranslator();
}
#Bean
public HibernateTransactionManager transactionManager()
{
// HERE THE EXCEPTION IS THROWN
HibernateTransactionManager htm = new HibernateTransactionManager(sessionFactory());
return htm;
}
}
This is the DAO UserDAO.java
package my.dal.dao;
import my.models.dal.User;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
#Repository
public class UserDAO
{
private SessionFactory sessionFactory;
#Autowired
public UserDAO(SessionFactory sessionFactory) {
this.sessionFactory=sessionFactory;
}
#Transactional
public int insert(User user) {
return (Integer) sessionFactory.getCurrentSession().save(user);
}
#Transactional
public User getByUsername(String username) {
return (User) sessionFactory.getCurrentSession().get(User.class, username);
}
#Transactional
public void update(User user) {
sessionFactory.getCurrentSession().merge(user);
}
#Transactional
public void delete(String username) {
User u = getByUsername(username);
sessionFactory.getCurrentSession().delete(u);
}
}
Finally, this is the test class DALTest.java
package my.dal.tests;
import static org.junit.Assert.assertTrue;
import my.dal.config.DALConfig;
import my.dal.config.HibernateConfig;
import my.dal.dao.UserDAO;
import my.models.dal.User;
import org.hibernate.SessionFactory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
#ContextConfiguration(classes = { DALConfig.class, HibernateConfig.class})
#RunWith(SpringJUnit4ClassRunner.class)
public class DALTest {
#Autowired
SessionFactory sessionFactory;
#Test
public void testGetUser() {
UserDAO userDAO = new UserDAO(sessionFactory);
User user = null;
user = userDAO.getByUsername("mrossi");
assertTrue(null != user);
}
}
The execution of the test ends with the following exception
...
Caused by: java.lang.IllegalArgumentException: Property 'sessionFactory' is required
at org.springframework.orm.hibernate4.HibernateTransactionManager.afterPropertiesSet(HibernateTransactionManager.java:247)
at org.springframework.orm.hibernate4.HibernateTransactionManager.<init>(HibernateTransactionManager.java:130)
at my.dal.config.HibernateConfig.transactionManager(HibernateConfig.java:66)
...
It is thrown at this line
HibernateTransactionManager htm = new HibernateTransactionManager(sessionFactory());
It seems like Spring cannot instantiate the sessionFactory bean but I don't know what could be the problem...
What do you think about that?
Thank you
You forgot to call
lsfb.afterPropertiesSet()
before getting the object from lsfb. afterPropertiesSet() is the method that builds and exposes the session factory.
One way to fix that is that you are using Constructor Injection for the sessionFactory which is not working with annotation exposed bean well. (Not sure how Spring 4 makes any improvement on that. I only used Spring 3.5 and below)
I would recommend to use getter/setter method injection for that in UserDAO.java
private SessionFactory sessionFactory;
#Autowired
public void setSessionFactory(SessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
So annotation can get the bean.
As JB Nizet suggested, you're missing the call to afterPropertiesSet()which is needed if you handle the object lifecycle yourself. I'd like to propose a slightly better version of your config to avoid this issue, which you might run into in other cases as well.
Whenever you configure a FactoryBean in JavaConfig, return the factory and refer to the actual object to be produced on the client methods. In your example this would look something like this:
#Configuration
class YourConfig {
#Bean
public LocalSessionFactoryBean sessionFactory() {
// setup factory
}
#Bean
public HibernateTransactionManager transactionManager(SessionFactory factory) {
return new HibernateTransactionManager(factory);
}
}
As you can see, we don't manually invoke any lifecycle callbacks on the factory as we have to return it as is. Spring will do that for us. It will also invoke ….getObject() on it to create the actual SessionFactory and hand it into the #Bean method to create the transaction manager.

Categories