3

I am attempting to create an OAuth2 Authorization Server using Spring Boot 2. However, whenever I attempt to start my application I receive an error:

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.

2018-08-17 08:17:53.946 ERROR 38996 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed



org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration': Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'appConfig': Unsatisfied dependency expressed through field 'accessTokenConverter'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authServerConfig' defined in file [C:\Users\Kelly Marchewa\workspace\Java\MidamAuth\bin\main\com\midamcorp\auth_server\config\AuthServerConfig.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'securityConfig': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'OAuthUserDetailsService': Unsatisfied dependency expressed through field 'userRepo'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository': Cannot create inner bean '(inner bean)#656d704' of type [org.springframework.orm.jpa.SharedEntityManagerCreator] while setting bean property 'entityManager'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name '(inner bean)#656d704': Cannot resolve reference to bean 'entityManagerFactory' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'entityManagerFactory': Requested bean is currently in creation: Is there an unresolvable circular reference?

    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:732) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:197) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1267) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1124) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:372) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) ~[spring-beans-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1089) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:859) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.8.RELEASE.jar:5.0.8.RELEASE]

    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]

    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]

    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]

    at org.springframework.boot.SpringApplication.run(SpringApplication.java:330) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]

    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1258) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]

    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1246) [spring-boot-2.0.4.RELEASE.jar:2.0.4.RELEASE]

    at com.midamcorp.auth_server.AuthServerApplication.main(AuthServerApplication.java:26) [main/:na]

    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]

    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]

    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]

    at java.base/java.lang.reflect.Method.invoke(Method.java:564) ~[na:na]

    at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.0.4.RELEASE.jar:2.0.4.RELEASE]

From the stack trace, the root of the issue appears to be in creating the entityManagerFactory bean. However, I cannot figure out why this would be an issue. The issue starts with my AppConfig, which references a JwtAccessTokenConverter found in my AuthServerConfig:

 @Configuration
public class AppConfig {

    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${spring.datasource.driverClassName}")
    private String dbDriverClassName;

    @Value("${spring.datasource.username}")
    private String dbUsername;

    @Value("${spring.datasource.password}")
    private String dbPassword;

    @Autowired
    JwtAccessTokenConverter accessTokenConverter;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public ModelMapper modelMapper() {
        ModelMapper mapper = new ModelMapper();
        return mapper;
    }

    @Bean
    public DataSource dataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(dbDriverClassName);
        dataSource.setUrl(datasourceUrl);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);

        return dataSource;
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter);
    }

}

AuthServer

 // Reference: https://dazito.com/java/spring-boot-and-oauth2-with-jdbc

@EnableAuthorizationServer
@Configuration
public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private PasswordEncoder passwordEncoder;

    private final AppConfig appConfig;

    @Autowired
    private OAuthUserDetailsService userService;

    private AuthenticationManager authenticationManager;

    @Autowired
    public AuthServerConfig(AuthenticationManager authenticationManager, AppConfig appConfig) {
        this.authenticationManager = authenticationManager;
        this.appConfig = appConfig;
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.passwordEncoder(passwordEncoder);
        security.checkTokenAccess("permitAll()");
        security.tokenKeyAccess("permitAll()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {

        // JdbcClientDetailsService details = new
        // JdbcClientDetailsService(appConfig.dataSource());
        // details.setPasswordEncoder(passwordEncoder);
        configurer.inMemory()
                .withClient("web")
                .secret(passwordEncoder.encode("secret"))
                .accessTokenValiditySeconds(60 * 10) // 10 minutes
                .refreshTokenValiditySeconds(60 * 60 * 12) // 12 hours
                .scopes("read", "write")
                .authorizedGrantTypes("password", "refresh_token");
        // configurer.withClientDetails(details);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(Arrays.asList(tokenEnhancer(), accessTokenConverter()));
        endpoints.tokenStore(tokenStore)
                .tokenEnhancer(tokenEnhancerChain)
                .userDetailsService(userService)
                .authenticationManager(authenticationManager);
    }

    @Bean
    @Primary // Making this primary to avoid any accidental duplication with another token
                // service instance of the same name
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore);
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnchancer();
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"),
                "mypass".toCharArray());

        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
        return converter;
    }
}

Which requires an UserDetailsService bean:

// Reference: http://www.baeldung.com/role-and-privilege-for-spring-security-registration
@Service
public class OAuthUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepo;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        OAuthUser user = userRepo.findByUserName(username);

        if(user == null) {
            throw new UsernameNotFoundException("Could not find " + username);
        }

        UserDetails details = new User(user.getUserName(), user.getPassword(), getGrantedAuthorities(user.getRoles()));
        return details;
    }


    private List<GrantedAuthority> getGrantedAuthorities(List<Role> roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();

        if(roles != null) {
            for(Role role: roles) {
                authorities.add(new SimpleGrantedAuthority(role.getRoleTitle()));       
            }
        }       
            return authorities; }


}

Which references a very basic CrudRepository.

public interface UserRepository extends CrudRepository<OAuthUser, Integer> {

    public OAuthUser findByUserName(String name);

    @Query("SELECT u FROM OAuthUser as u JOIN u.roles as r WHERE r.id = ?1 AND u.isEnabled = true")
    public List<OAuthUser> findByRole(long roleID);
}

Certainly, there is a "chain" of dependencies, but I do not see any circular references. Furthermore, this application was working. While I have made other changes, the issue arose while attempting to migrate the application from Spring Boot 1.5 and Java 8 to Spring Boot 2 and Java 10.

I am not sure if it might be some compatability issues with some of my dependencies?

build.gradle

buildscript {
    ext {
        springBootVersion = '2.0.4.RELEASE'
    }
    repositories {
        mavenCentral()
        maven { url "https://repo.spring.io/snapshot" }
        maven { url "https://repo.spring.io/milestone" }
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}


apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
apply plugin: 'application'
mainClassName = 'com.midamcorp.auth_server.AuthServerApplication'

group = 'com.midamcorp'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 10

repositories {
    mavenCentral()
    maven { url "https://repo.spring.io/snapshot" }
    maven { url "https://repo.spring.io/milestone" }
}




dependencies {
    compile('org.springframework.boot:spring-boot-starter-data-rest')
    compile('org.springframework.boot:spring-boot-starter-jdbc')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('org.springframework.boot:spring-boot-starter-security')
    compile('commons-io:commons-io:2.5')    
    compile ('org.modelmapper:modelmapper:1.0.0')   
    compile('org.springframework.security:spring-security-jwt:1.0.9.RELEASE')
    compile('org.springframework.security.oauth:spring-security-oauth2:2.3.3.RELEASE') 
    runtime('org.springframework.boot:spring-boot-devtools')
    compile ('org.apache.httpcomponents:httpclient:4.5.5')
    runtime('org.postgresql:postgresql')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.springframework.security:spring-security-test')    
    runtime('javax.servlet:javax.servlet-api:4.0.1')


}

I would appreciate any help. Thanks.

KellyM
  • 2,472
  • 6
  • 46
  • 90
  • 1
    Why do you create your own dataSource? This is autoconfigured by Spring Boot Data JPA – Simon Martinelli Aug 17 '18 at 14:01
  • @SimonMartinelli, thanks for noticing. I am not positive why the dataSource was there, as this was a project I started a while ago (and it never did any harm before). I have removed it, but now I am getting a circular reference error relating to the `passwordEncoder`. Again, this was not an issue before I made the migration to Spring Boot 2/Java 10. Has the framework made any changes in the way it resolves beans? I did not notice anything that I thought would cause the observed issue in Spring's migration guide. Thanks. – KellyM Aug 17 '18 at 14:56

2 Answers2

0

Move the DataSource into a separate config file that only contains the DataSource bean and the environment values. I believe you have a circular dependency somewhere around JwtAccessTokenConverter.

I had a similar problem when I was following probably the same tutorial.

@Configuration
public class DataSourceConfig {

    @Value("${spring.datasource.url}")
    private String datasourceUrl;

    @Value("${spring.datasource.driverClassName}")
    private String dbDriverClassName;

    @Value("${spring.datasource.username}")
    private String dbUsername;

    @Value("${spring.datasource.password}")
    private String dbPassword;


    @Bean
    public DataSource dataSource() {
        final DriverManagerDataSource dataSource = new DriverManagerDataSource();

        dataSource.setDriverClassName(dbDriverClassName);
        dataSource.setUrl(datasourceUrl);
        dataSource.setUsername(dbUsername);
        dataSource.setPassword(dbPassword);

        return dataSource;
    } 

}
KevKosDev
  • 811
  • 2
  • 9
  • 31
0

If you have following properties defined in application.properties file you do not need to Datasource Bean again.

spring.datasource.url

spring.datasource.username

spring.datasource.password

spring.datasource.driver-class-name

whenever you need to refer datasource just autowire it and use as follows.

import javax.sql.DataSource;

@Autowired
private DataSource dataSource;

@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception 
{

    clients.jdbc(dataSource);
}