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
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/