Search code examples
springspring-bootspring-data-jpa

Cannot load configs from Config Server - No suitable HttpMessageConverter


I can reach my Config Server at http://localhost:8888, but I can’t load the desired configurations from my location-service.yml file.

My Configuration Server (CNF-S)

application.yml:

# Basic Configuration - Spring Config Server (CNF-S)
spring:
    application:
        name: Configuration
    profiles:
      active:
        - ${ENVIRONMENT} # Which is "dev" currently
      default: dev

# GitHub Repository (private & SSH)
    cloud:
      config:
        server:
          git:
            uri: [email protected]:*****/EMP_CNF-S.git # Private git repo
            ignore-local-ssh-settings: true
            default-label: ${ENVIRONMENT}
            private-key: ${GIT_SSH_KEY}
            search-paths: src/main/resources
            skip-ssl-validation: true
            timeout: 10
            clone-on-start: true

  # Spring Security
    security:
      user:
        name: ${EMP_CONFIG_USERNAME}
        password: ${EMP_CONFIG_PASSWORD}

# Server port
server:
  port: 8888

location-service.yml on Config Server (CNF-S):

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/location_db
    username: root
    password: test-123!

  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true

  test:
    database:
      replace: none

My Client - Location Service (LOC-S)

bootstrap.yml:

# Basic configs - Name & Profile
spring:
  application:
    name: location-service
  profiles:
    active:
      - ${ENVIRONMENT}

# Connecting to CNF-S - Configuration Server
  config:
    import: optional:configserver:${BASE_URL}:8888
  cloud:
    config:
      label: ${ENVIRONMENT}
      uri: ${BASE_URL}:8888
      name: location-service
      username: ${EMP_CONFIG_USERNAME}
      password: ${EMP_CONFIG_PASSWORD}

# Configuring Port for Location-Service
server:
  port: 8080

# Spring Actuator
management:
  endpoints:
    web:
      exposure:
        include:
          - health
          - info
      base-path: /actuator
  endpoint:
    health:
      show-details: always

Issue Description

I am able to log into my Config Server and see my configurations at http://localhost:8888/location-service/dev. However, when my client tries to fetch the configurations, I receive the following error:

2024-09-23T15:38:53.706+02:00  INFO 43435 --- [location-service] [  restartedMain] c.c.c.ConfigServicePropertySourceLocator : Fetching config from server at : http://localhost:8888
2024-09-23T15:38:53.712+02:00  WARN 43435 --- [location-service] [  restartedMain] c.c.c.ConfigServicePropertySourceLocator : Could not locate PropertySource: Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.cloud.config.environment.Environment] and content type [text/html;charset=UTF-8]
2024-09-23T15:38:53.714+02:00  INFO 43435 --- [location-service] [  restartedMain] com.emp.loc.LocationApplication          : The following 1 profile is active: "dev"
2024-09-23T15:38:53.719+02:00  INFO 43435 --- [location-service] [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://localhost:8888
2024-09-23T15:38:53.719+02:00  WARN 43435 --- [location-service] [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Could not locate PropertySource ([ConfigServerConfigDataResource@265110bb uris = array<String>['http://localhost:8888'], optional = true, profiles = 'default']): Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.cloud.config.environment.Environment] and content type [text/html;charset=UTF-8]
2024-09-23T15:38:53.719+02:00  INFO 43435 --- [location-service] [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://localhost:8888
2024-09-23T15:38:53.719+02:00  WARN 43435 --- [location-service] [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Could not locate PropertySource ([ConfigServerConfigDataResource@3373a584 uris = array<String>['http://localhost:8888'], optional = true, profiles = 'dev']): Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.cloud.config.environment.Environment] and content type [text/html;charset=UTF-8]
2024-09-23T15:38:53.719+02:00  INFO 43435 --- [location-service] [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Fetching config from server at : http://localhost:8888
2024-09-23T15:38:53.719+02:00  WARN 43435 --- [location-service] [  restartedMain] o.s.c.c.c.ConfigServerConfigDataLoader   : Could not locate PropertySource ([ConfigServerConfigDataResource@1d09bf99 uris = array<String>['http://localhost:8888'], optional = true, profiles = 'default']): Could not extract response: no suitable HttpMessageConverter found for response type [class org.springframework.cloud.config.environment.Environment] and content type [text/html;charset=UTF-8]
2024-09-23T15:38:54.010+02:00  INFO 43435 --- [location-service] [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2024-09-23T15:38:54.016+02:00  INFO 43435 --- [location-service] [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 2 ms. Found 0 JPA repository interfaces.
2024-09-23T15:38:54.077+02:00  INFO 43435 --- [location-service] [  restartedMain] o.s.cloud.context.scope.GenericScope     : BeanFactory id=845d36ed-6367-395b-a199-708537ceebfe
2024-09-23T15:38:54.392+02:00  INFO 43435 --- [location-service] [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port 8080 (http)
2024-09-23T15:38:54.400+02:00  INFO 43435 --- [location-service] [  restartedMain] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2024-09-23T15:38:54.400+02:00  INFO 43435 --- [location-service] [  restartedMain] o.apache.catalina.core.StandardEngine    : Starting Servlet engine: [Apache Tomcat/10.1.28]
2024-09-23T15:38:54.417+02:00  INFO 43435 --- [location-service] [  restartedMain] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2024-09-23T15:38:54.418+02:00  INFO 43435 --- [location-service] [  restartedMain] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 698 ms
2024-09-23T15:38:54.494+02:00  WARN 43435 --- [location-service] [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Failed to initialize dependency 'dataSourceScriptDatabaseInitializer' of LoadTimeWeaverAware bean 'entityManagerFactory': Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Unsatisfied dependency expressed through method 'dataSourceScriptDatabaseInitializer' parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to determine a suitable driver class
2024-09-23T15:38:54.495+02:00  INFO 43435 --- [location-service] [  restartedMain] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2024-09-23T15:38:54.511+02:00  INFO 43435 --- [location-service] [  restartedMain] .s.b.a.l.ConditionEvaluationReportLogger : 

Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled.
2024-09-23T15:38:54.518+02:00 ERROR 43435 --- [location-service] [  restartedMain] o.s.b.d.LoggingFailureAnalysisReporter   : 

***************************
APPLICATION FAILED TO START
***************************

Description:

Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.

Reason: Failed to determine a suitable driver class


Action:

Consider the following:
    If you want an embedded database (H2, HSQL or Derby), please put it on the classpath.
    If you have database settings to be loaded from a particular profile you may need to activate it (the profiles dev are currently active).


Process finished with exit code 0

PS: The value of ${EMP_CONFIG_PASSWORD} is the hashed value of my plaintext password => $2a$12$a.2zjPN5uKwAfi*********

My Config Server has a SecurityConfiguration.java class which looks like this:

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    // Loading environment variables - Username & Password
    @Value("${EMP_CONFIG_USERNAME}")
    private String envUsername;

    @Value("${EMP_CONFIG_PASSWORD}")
    private String envPassword;

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

    // SecurityFilterChain
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{

        // Checking if username and password are available
        boolean userInMemoryAuth = !envUsername.isEmpty() && !envPassword.isEmpty();

        // Security setup based on environment
        http
                .csrf(csrf -> csrf.disable())
                .cors(cors -> cors.configurationSource(request -> {
                    var corsConfig = new CorsConfiguration();
                    corsConfig.addAllowedOrigin("http://localhost:8888");
                    corsConfig.addAllowedHeader("*");
                    corsConfig.addAllowedMethod("*");
                    corsConfig.setAllowCredentials(true);
                return corsConfig;
                }));

        if (userInMemoryAuth){
            http.authorizeHttpRequests(authorizeRequest ->
                    authorizeRequest.anyRequest().authenticated())
                    .formLogin(withDefaults());
        }else{
            http.authorizeHttpRequests(authorizeRequests ->
                    authorizeRequests.anyRequest().permitAll());
            // TODO: Add JWT Authentication setup between Gateway Service and Configuration Service
        }
        return  http.build();
    }

    // Authentication Provider - UserDetailService (InMemory)
    @Bean
    public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
        if(!envUsername.isEmpty() && !envPassword.isEmpty()){
            UserDetails user = User
                    .withUsername(envUsername)
                    .password(envPassword)
                    .roles("ADMIN")
                    .build();
            return new InMemoryUserDetailsManager(user);
        }
        return new InMemoryUserDetailsManager();
    }
}

Solution

  • I could manage to solve the issue. In my SecurityConfiguration.java I've changed the "http.formLogin" -> "http.htpBasic". Then it worked.

    The root cause is that since my Spring Security enables a form login which can't be submitted by http post requests from postman or the Spring Config Client itself.

    Now with this setup for Spring Security it worked fine: SecurityConfiguration.java:

    package com.emp.config.security;
    
    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.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.core.userdetails.User;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.provisioning.InMemoryUserDetailsManager;
    import org.springframework.security.web.SecurityFilterChain;
    import org.springframework.web.cors.CorsConfiguration;
    
    import static org.springframework.security.config.Customizer.withDefaults;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfiguration {
    
        // Loading environment variables - Username & Password
        @Value("${EMP_CONFIG_USERNAME}")
        private String envUsername;
    
        @Value("${EMP_CONFIG_PASSWORD}")
        private String envPassword;
    
        // Password Encoder
        @Bean
        public PasswordEncoder passwordEncoder(){
            return new BCryptPasswordEncoder();
        }
    
        // SecurityFilterChain
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
    
            // Checking if username and password are available
            boolean userInMemoryAuth = !envUsername.isEmpty() && !envPassword.isEmpty();
    
            // Security setup based on environment
            http
                    .csrf(csrf -> csrf.disable())
                    .cors(cors -> cors.configurationSource(request -> {
                        var corsConfig = new CorsConfiguration();
                        corsConfig.addAllowedOrigin("http://localhost:8888");
                        corsConfig.addAllowedOrigin("http://localhost:8080");
                        corsConfig.addAllowedHeader("*");
                        corsConfig.addAllowedMethod("*");
                        corsConfig.setAllowCredentials(true);
                    return corsConfig;
                    }));
    
            if (userInMemoryAuth){
                http.authorizeHttpRequests(authorizeRequest ->
                        authorizeRequest.anyRequest().authenticated())
                        .httpBasic(withDefaults());
            }else{
                http.authorizeHttpRequests(authorizeRequests ->
                        authorizeRequests.anyRequest().permitAll());
                // TODO: Add JWT Authentication setup between Gateway Service and Configuration Service
            }
            return  http.build();
        }
    
        // Authentication Provider - UserDetailService (InMemory)
        @Bean
        public InMemoryUserDetailsManager inMemoryUserDetailsManager(){
            if(!envUsername.isEmpty() && !envPassword.isEmpty()){
                UserDetails user = User
                        .withUsername(envUsername)
                        .password(envPassword) // Encode the password here
                        .roles("ADMIN")
                        .build();
                return new InMemoryUserDetailsManager(user);
            }
            return new InMemoryUserDetailsManager();
        }
    }