java.net.ConnectException: Connection refused when using docker-compose with Spring Boot and Keycloak

122 views Asked by At

I do have a couple of Spring Boot services as part of a docker-compose file. Unfortunately some (the ones that act as an OAuth client) are failing at startup with

Caused by: java.lang.IllegalArgumentException: Unable to resolve Configuration with the provided Issuer of "http://iam-service:8080/realms/OptionAdvisor"
        at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:228) ~[spring-security-oauth2-client-6.0.2.jar:6.0.2]
        at org.springframework.security.oauth2.client.registration.ClientRegistrations.fromIssuerLocation(ClientRegistrations.java:152) ~[spring-security-oauth2-client-6.0.2.jar:6.0.2]
        at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getBuilderFromIssuerIfPossible(OAuth2ClientPropertiesRegistrationAdapter.java:86) ~[spring-boot-autoconfigure-3.0.4.jar:3.0.4]
        at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getClientRegistration(OAuth2ClientPropertiesRegistrationAdapter.java:60) ~[spring-boot-autoconfigure-3.0.4.jar:3.0.4]
        at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.lambda$getClientRegistrations$0(OAuth2ClientPropertiesRegistrationAdapter.java:54) ~[spring-boot-autoconfigure-3.0.4.jar:3.0.4]
        at java.base/java.util.HashMap.forEach(HashMap.java:1425) ~[na:na]
        at org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(OAuth2ClientPropertiesRegistrationAdapter.java:53) ~[spring-boot-autoconfigure-3.0.4.jar:3.0.4]
        at org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientRegistrationRepositoryConfiguration.clientRegistrationRepository(OAuth2ClientRegistrationRepositoryConfiguration.java:49) ~[spring-boot-autoconfigure-3.0.4.jar:3.0.4]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[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:568) ~[na:na]
        at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:139) ~[spring-beans-6.0.6.jar:6.0.6]
        ... 81 common frames omitted
Caused by: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://iam-service:8080/realms/OptionAdvisor/.well-known/openid-configuration": Connection refused
        at org.springframework.web.client.RestTemplate.createResourceAccessException(RestTemplate.java:888) ~[spring-web-6.0.6.jar:6.0.6]
        at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:868) ~[spring-web-6.0.6.jar:6.0.6]
        at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:714) ~[spring-web-6.0.6.jar:6.0.6]
        at org.springframework.security.oauth2.client.registration.ClientRegistrations.lambda$oidc$0(ClientRegistrations.java:163) ~[spring-security-oauth2-client-6.0.2.jar:6.0.2]
        at org.springframework.security.oauth2.client.registration.ClientRegistrations.getBuilder(ClientRegistrations.java:216) ~[spring-security-oauth2-client-6.0.2.jar:6.0.2]
        ... 93 common frames omitted
Caused by: java.net.ConnectException: Connection refused
        at java.base/sun.nio.ch.Net.pollConnect(Native Method) ~[na:na]
        at java.base/sun.nio.ch.Net.pollConnectNow(Net.java:669) ~[na:na]
        at java.base/sun.nio.ch.NioSocketImpl.timedFinishConnect(NioSocketImpl.java:542) ~[na:na]
        at java.base/sun.nio.ch.NioSocketImpl.connect(NioSocketImpl.java:597) ~[na:na]
        at java.base/java.net.Socket.connect(Socket.java:630) ~[na:na]
        at java.base/sun.net.NetworkClient.doConnect(NetworkClient.java:177) ~[na:na]
        at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:497) ~[na:na]
        at java.base/sun.net.www.http.HttpClient.openServer(HttpClient.java:600) ~[na:na]
        at java.base/sun.net.www.http.HttpClient.<init>(HttpClient.java:246) ~[na:na]
        at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:351) ~[na:na]
        at java.base/sun.net.www.http.HttpClient.New(HttpClient.java:372) ~[na:na]
        at java.base/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1299) ~[na:na]
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1232) ~[na:na]
        at java.base/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1120) ~[na:na]
        at java.base/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1051) ~[na:na]
        at org.springframework.http.client.SimpleBufferingClientHttpRequest.executeInternal(SimpleBufferingClientHttpRequest.java:75) ~[spring-web-6.0.6.jar:6.0.6]
        at org.springframework.http.client.AbstractBufferingClientHttpRequest.executeInternal(AbstractBufferingClientHttpRequest.java:48) ~[spring-web-6.0.6.jar:6.0.6]
        at org.springframework.http.client.AbstractClientHttpRequest.execute(AbstractClientHttpRequest.java:66) ~[spring-web-6.0.6.jar:6.0.6]
        at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:862) ~[spring-web-6.0.6.jar:6.0.6]
        ... 96 common frames omitted

I have no clue why I am seeing http://iam-service:8080/realms/OptionAdvisor/.well-known/openid-configuration": Connection refused. When I do execute wget http://iam-service:8080/realms/OptionAdvisor/.well-known/openid-configuration from another container started by the same docker-compose.yml that is part of the same (default) network, I am getting the expected response. Same when I access http://localhost:8080/realms/OptionAdvisor/.well-known/openid-configuration in the browser of my host machine.

Do you have any hints for me what could cause this? What would you do next to debug this?

This is how my docker-compose.yml file looks like (ranking-service, decision-service, and compare-decision-service are affected by this issue):

version: '3.8'
services:
  db-service:
    image: postgres:15.2
    environment:
      - POSTGRES_USER=${POSTGRESDB_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_MULTIPLE_DATABASES=${KC_DB_NAME},${RANKING_STRATEGY_SERVICE_DB_NAME},${COMPARE_DECISION_SERVICE_DB_NAME},${SECRET_SERVICE_DB_NAME},${RANKING_SERVICE_DB_NAME},${DECISION_SERVICE_DB_NAME}
    ports:
      - ${POSTGRESDB_LOCAL_PORT}:${POSTGRESDB_DOCKER_PORT}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./postgresinit/:/docker-entrypoint-initdb.d
  iam-service:
    image: quay.io/keycloak/keycloak:23.0.0
    volumes:
      - ./imports/OptionAdvisorRealm.json:/opt/keycloak/data/import/OptionAdvisorRealm.json
    environment:     
      - KEYCLOAK_ADMIN=${KC_ADMIN}
      - KEYCLOAK_ADMIN_PASSWORD=${KC_ADMIN_PASSWORD}
      - KC_DB=${KC_DB}
      - KC_DB_USERNAME=${POSTGRESDB_USER}
      - KC_DB_PASSWORD=${POSTGRES_PASSWORD}
      - KC_DB_URL=${KC_DB_URL}
    ports:
      - ${KC_LOCAL_PORT}:${KC_DOCKER_PORT}
    entrypoint: "/opt/keycloak/bin/kc.sh start-dev --import-realm"
    depends_on:
      - db-service
  ranking-strategy-service:
    build:
      context: ../ranking-strategy-service
      dockerfile: Dockerfile
    ports:
      - ${RANKING_STRATEGY_SERVICE_LOCAL_PORT}:${RANKING_STRATEGY_SERVICE_DOCKER_PORT}
    depends_on:
      - iam-service
      - db-service
    environment:
      - SPRING_DATASOURCE_URL=${RANKING_STRATEGY_SERVICE_DATASOURCE_URL}
      - SPRING_DATASOURCE_USERNAME=${POSTGRESDB_USER}
      - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
      - IAM_SERVICE.BASE_URL=${KC_BASE_URL}
      - SPRING_PROFILES_ACTIVE=dev
  secret-service:
    build:
      context: ../secret-service
      dockerfile: Dockerfile
    ports:
      - ${SECRET_SERVICE_LOCAL_PORT}:${SECRET_SERVICE_DOCKER_PORT}
    depends_on:
      - iam-service
      - db-service
    environment:
      - SPRING_DATASOURCE_URL=${SECRET_SERVICE_DATASOURCE_URL}
      - SPRING_DATASOURCE_USERNAME=${POSTGRESDB_USER}
      - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
      - IAM_SERVICE.BASE_URL=${KC_BASE_URL}
      - SPRING_PROFILES_ACTIVE=dev
  compare-decision-service:
    build:
      context: ../compare-decision-service
      dockerfile: Dockerfile
    ports:
      - ${COMPARE_DECISION_SERVICE_LOCAL_PORT}:${COMPARE_DECISION_SERVICE_DOCKER_PORT}
    depends_on:
      - iam-service
      - ranking-strategy-service
      - db-service
      - secret-service
    environment:
      - SPRING_DATASOURCE_URL=${COMPARE_DECISION_SERVICE_DATASOURCE_URL}
      - SPRING_DATASOURCE_USERNAME=${POSTGRESDB_USER}
      - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
      - IAM_SERVICE.BASE_URL=${KC_BASE_URL}
      - SECRET_SERVICE.BASE_URL=${SECRET_SERVICE_BASE_URL}
      - RANKING_STRATEGY_SERVICE.BASE_URL=${RANKING_STRATEGY_SERVICE_BASE_URL}
      - DECISION_SERVICE.BASE_URL=${DECISION_SERVICE_BASE_URL}
      - RANKING_SERVICE.BASE_URL=${RANKING_SERVICE_BASE_URL}
      - SPRING_PROFILES_ACTIVE=dev      
  ranking-service:
    build:
      context: ../ranking-service
      dockerfile: Dockerfile
    ports:
      - ${RANKING_SERVICE_LOCAL_PORT}:${RANKING_SERVICE_DOCKER_PORT}
    depends_on:
      - iam-service
      - db-service
      - secret-service
    environment:
      - SPRING_DATASOURCE_URL=${RANKING_SERVICE_DATASOURCE_URL}
      - SPRING_DATASOURCE_USERNAME=${POSTGRESDB_USER}
      - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
      - IAM_SERVICE.BASE_URL=${KC_BASE_URL}
      - SECRET_SERVICE.BASE_URL=${SECRET_SERVICE_BASE_URL}
      - SPRING_PROFILES_ACTIVE=dev
  decision-service:
    build:
      context: ../decision-service
      dockerfile: Dockerfile
    ports:
      - ${DECISION_SERVICE_LOCAL_PORT}:${DECISION_SERVICE_DOCKER_PORT}
    depends_on:
      - iam-service
      - db-service
      - secret-service
      - ranking-service
      - ranking-strategy-service
    environment:
      - SPRING_DATASOURCE_URL=${DECISION_SERVICE_DATASOURCE_URL}
      - SPRING_DATASOURCE_USERNAME=${POSTGRESDB_USER}
      - SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
      - IAM_SERVICE.BASE_URL=${KC_BASE_URL}
      - SECRET_SERVICE.BASE_URL=${SECRET_SERVICE_BASE_URL}
      - RANKING_SERVICE.BASE_URL=${RANKING_SERVICE_BASE_URL}
      - SPRING_PROFILES_ACTIVE=dev
volumes:
  pgdata:

This is how my .env file looks like

POSTGRESDB_USER=admin
POSTGRES_PASSWORD=password
POSTGRESDB_LOCAL_PORT=5432
POSTGRESDB_DOCKER_PORT=5432
POSTGRESDB_BASE_URL=postgresql://db-service:${POSTGRESDB_DOCKER_PORT}

KC_ADMIN=admin
KC_ADMIN_PASSWORD=password
KC_DB=postgres
KC_DB_NAME=iam_service_db
KC_DB_URL=jdbc:${POSTGRESDB_BASE_URL}/${KC_DB_NAME}
KC_LOCAL_PORT=8080
KC_DOCKER_PORT=8080
KC_BASE_URL=http://iam-service:${KC_DOCKER_PORT}

RANKING_STRATEGY_SERVICE_LOCAL_PORT=8083
RANKING_STRATEGY_SERVICE_DOCKER_PORT=8083
RANKING_STRATEGY_SERVICE_DB_NAME=ranking_strategy_service_db
RANKING_STRATEGY_SERVICE_DATASOURCE_URL=jdbc:${POSTGRESDB_BASE_URL}/${RANKING_STRATEGY_SERVICE_DB_NAME}
RANKING_STRATEGY_SERVICE_BASE_URL=http://ranking-strategy-service:${RANKING_STRATEGY_SERVICE_DOCKER_PORT}

SECRET_SERVICE_LOCAL_PORT=8082
SECRET_SERVICE_DOCKER_PORT=8082
SECRET_SERVICE_DB_NAME=secret_service_db
SECRET_SERVICE_DATASOURCE_URL=jdbc:${POSTGRESDB_BASE_URL}/${SECRET_SERVICE_DB_NAME}
SECRET_SERVICE_BASE_URL=http://secret-service:${SECRET_SERVICE_DOCKER_PORT}

COMPARE_DECISION_SERVICE_LOCAL_PORT=8084
COMPARE_DECISION_SERVICE_DOCKER_PORT=8084
COMPARE_DECISION_SERVICE_DB_NAME=compare_decision_service_db
COMPARE_DECISION_SERVICE_DATASOURCE_URL=jdbc:${POSTGRESDB_BASE_URL}/${COMPARE_DECISION_SERVICE_DB_NAME}

RANKING_SERVICE_LOCAL_PORT=8085
RANKING_SERVICE_DOCKER_PORT=8085
RANKING_SERVICE_DB_NAME=ranking_service_db
RANKING_SERVICE_DATASOURCE_URL=jdbc:${POSTGRESDB_BASE_URL}/${RANKING_SERVICE_DB_NAME}
RANKING_SERVICE_BASE_URL=http://ranking-service:${RANKING_SERVICE_DOCKER_PORT}

DECISION_SERVICE_LOCAL_PORT=8081
DECISION_SERVICE_DOCKER_PORT=8081
DECISION_SERVICE_DB_NAME=decision_service_db
DECISION_SERVICE_DATASOURCE_URL=jdbc:${POSTGRESDB_BASE_URL}/${DECISION_SERVICE_DB_NAME}
DECISION_SERVICE_BASE_URL=http://decisiony-service:${DECISION_SERVICE_DOCKER_PORT}

This is how my application.yml file of ranking-service looks like

iam-service:
  base-url: http://localhost:8080
secret-service:
  base-url: http://localhost:8082
spring:
  datasource:
    url: "jdbc:postgresql://localhost:5432/decision-service"
    username: postgres
  application:
    name: ranking-service
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: ${iam-service.base-url}/realms/OptionAdvisor
          jwk-set-uri: ${iam-service.base-url}/realms/OptionAdvisor/protocol/openid-connect/certs
      client:
        registration:
          ranking-service:
            client-id: ${spring.application.name}
            client-secret: ranking-service-secret
            authorization-grant-type: client_credentials
        provider:
          ranking-service:
            issuer-uri: ${iam-service.base-url}/realms/OptionAdvisor
            token-uri: ${iam-service.base-url}/realms/OptionAdvisor/protocol/openid-connect/token
            user-name-attribute: preferred_username
server:
  servlet:
    context-path: /ranking_service/api
  port: 8085

This is how my application-dev.yml file of ranking-service looks like

logging:
  level:
    org:
      hibernate:
        SQL: TRACE
      x:
        optionadvisor:
          rankingservice: DEBUG
spring:
  jpa:
    hibernate:
      ddl-auto: create-drop
1

There are 1 answers

0
hyperion On

The issue is that docker-compose depends on does not work as I initially thought. I thought the start of a container would wait for a complete start of all dependent containers. But it seems like this needs to be added via healthchecks:

https://docs.docker.com/compose/startup-order/