From 985c5a34790396ed432be1eda462f11db7d8dbb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 15 Jan 2025 19:05:49 +0100 Subject: [PATCH] feat(oidc): Reuse Vert.x in DEV and Test mode when possible --- .../devservices/AbstractDevUIProcessor.java | 5 +- .../devservices/OidcDevUIProcessor.java | 88 ++-------- .../keycloak/KeycloakDevUIProcessor.java | 2 +- .../runtime/devui/OidcDevJsonRpcService.java | 17 +- .../oidc/runtime/devui/OidcDevUiRecorder.java | 46 ++++- .../java/io/quarkus/it/oidc/OidcMtlsTest.java | 160 +++++++++--------- .../test/oidc/client/OidcTestClient.java | 31 +++- 7 files changed, 172 insertions(+), 177 deletions(-) diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java index e7342b6f7727c..383dd938ddfc3 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/AbstractDevUIProcessor.java @@ -46,7 +46,7 @@ protected static CardPageBuildItem createProviderWebComponent(OidcDevUiRecorder Map keycloakUsers, List keycloakRealms, boolean alwaysLogoutUserInDevUiOnReload, - HttpConfiguration httpConfiguration) { + HttpConfiguration httpConfiguration, boolean discoverMetadata, String authServerUrl) { final CardPageBuildItem cardPage = new CardPageBuildItem(); // prepare provider component @@ -82,7 +82,8 @@ protected static CardPageBuildItem createProviderWebComponent(OidcDevUiRecorder authorizationUrl, tokenUrl, logoutUrl, webClientTimeout, grantOptions, keycloakUsers, oidcProviderName, oidcApplicationType, oidcGrantType, introspectionIsAvailable, keycloakAdminUrl, keycloakRealms, swaggerIsAvailable, - graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload); + graphqlIsAvailable, swaggerUiPath, graphqlUiPath, alwaysLogoutUserInDevUiOnReload, discoverMetadata, + authServerUrl); recorder.createJsonRPCService(beanContainer.getValue(), runtimeProperties, httpConfiguration); diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java index 7223bdc5e018f..687ef5e9b9859 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/OidcDevUIProcessor.java @@ -4,7 +4,6 @@ import java.util.Set; import org.eclipse.microprofile.config.ConfigProvider; -import org.jboss.logging.Logger; import io.quarkus.arc.deployment.BeanContainerBuildItem; import io.quarkus.deployment.Capabilities; @@ -15,32 +14,22 @@ import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ConfigurationBuildItem; -import io.quarkus.deployment.builditem.CuratedApplicationShutdownBuildItem; import io.quarkus.deployment.builditem.RuntimeConfigSetupCompleteBuildItem; import io.quarkus.devservices.oidc.OidcDevServicesConfigBuildItem; import io.quarkus.devui.spi.JsonRPCProvidersBuildItem; import io.quarkus.devui.spi.page.CardPageBuildItem; import io.quarkus.oidc.OidcTenantConfig; import io.quarkus.oidc.OidcTenantConfig.Provider; -import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.oidc.deployment.OidcBuildTimeConfig; import io.quarkus.oidc.runtime.devui.OidcDevJsonRpcService; -import io.quarkus.oidc.runtime.devui.OidcDevServicesUtils; import io.quarkus.oidc.runtime.devui.OidcDevUiRecorder; import io.quarkus.oidc.runtime.providers.KnownOidcProviders; import io.quarkus.runtime.configuration.ConfigUtils; +import io.quarkus.vertx.core.deployment.CoreVertxBuildItem; import io.quarkus.vertx.http.deployment.NonApplicationRootPathBuildItem; import io.quarkus.vertx.http.runtime.HttpConfiguration; -import io.vertx.core.Vertx; -import io.vertx.core.http.HttpHeaders; -import io.vertx.core.json.JsonObject; -import io.vertx.mutiny.core.buffer.Buffer; -import io.vertx.mutiny.ext.web.client.HttpResponse; -import io.vertx.mutiny.ext.web.client.WebClient; public class OidcDevUIProcessor extends AbstractDevUIProcessor { - static volatile Vertx vertxInstance; - private static final Logger LOG = Logger.getLogger(OidcDevUIProcessor.class); private static final String TENANT_ENABLED_CONFIG_KEY = CONFIG_PREFIX + "tenant-enabled"; private static final String DISCOVERY_ENABLED_CONFIG_KEY = CONFIG_PREFIX + "discovery-enabled"; @@ -57,9 +46,9 @@ public class OidcDevUIProcessor extends AbstractDevUIProcessor { @Record(ExecutionTime.RUNTIME_INIT) @BuildStep(onlyIf = IsDevelopment.class) + @Consume(CoreVertxBuildItem.class) // metadata discovery requires Vertx instance @Consume(RuntimeConfigSetupCompleteBuildItem.class) - void prepareOidcDevConsole(CuratedApplicationShutdownBuildItem closeBuildItem, - Capabilities capabilities, + void prepareOidcDevConsole(Capabilities capabilities, HttpConfiguration httpConfiguration, BeanContainerBuildItem beanContainer, NonApplicationRootPathBuildItem nonApplicationRootPathBuildItem, @@ -76,33 +65,8 @@ void prepareOidcDevConsole(CuratedApplicationShutdownBuildItem closeBuildItem, ? oidcDevServicesConfigBuildItem.get().getConfig().get(AUTH_SERVER_URL_CONFIG_KEY) : getAuthServerUrl(providerConfig); if (authServerUrl != null) { - if (vertxInstance == null) { - vertxInstance = Vertx.vertx(); - - Runnable closeTask = new Runnable() { - @Override - public void run() { - if (vertxInstance != null) { - try { - vertxInstance.close(); - } catch (Throwable t) { - LOG.error("Failed to close Vertx instance", t); - } - } - vertxInstance = null; - } - }; - closeBuildItem.addCloseTask(closeTask, true); - } - JsonObject metadata = null; - if (isDiscoveryEnabled(providerConfig)) { - metadata = discoverMetadata(authServerUrl); - if (metadata == null) { - return; - } - } + boolean discoverMetadata = isDiscoveryEnabled(providerConfig); String providerName = tryToGetProviderName(authServerUrl); - boolean metadataNotNull = metadata != null; final String keycloakAdminUrl; if (KEYCLOAK.equals(providerName)) { @@ -116,12 +80,10 @@ public void run() { getApplicationType(providerConfig), oidcConfig.devui().grant().type().isPresent() ? oidcConfig.devui().grant().type().get().getGrantType() : "code", - metadataNotNull ? metadata.getString("authorization_endpoint") : null, - metadataNotNull ? metadata.getString("token_endpoint") : null, - metadataNotNull ? metadata.getString("end_session_endpoint") : null, - metadataNotNull - ? (metadata.containsKey("introspection_endpoint") || metadata.containsKey("userinfo_endpoint")) - : checkProviderUserInfoRequired(providerConfig), + null, + null, + null, + checkProviderUserInfoRequired(providerConfig), beanContainer, oidcConfig.devui().webClientTimeout(), oidcConfig.devui().grantOptions(), @@ -131,7 +93,7 @@ public void run() { null, null, true, - httpConfiguration); + httpConfiguration, discoverMetadata, authServerUrl); cardPageProducer.produce(cardPage); } } @@ -141,14 +103,14 @@ JsonRPCProvidersBuildItem produceOidcDevJsonRpcService() { return new JsonRPCProvidersBuildItem(OidcDevJsonRpcService.class); } - private boolean checkProviderUserInfoRequired(OidcTenantConfig providerConfig) { + private static boolean checkProviderUserInfoRequired(OidcTenantConfig providerConfig) { if (providerConfig != null) { - return providerConfig.authentication.userInfoRequired.orElse(false); + return providerConfig.authentication().userInfoRequired().orElse(false); } return false; } - private String tryToGetProviderName(String authServerUrl) { + private static String tryToGetProviderName(String authServerUrl) { if (authServerUrl.contains("/realms/")) { return KEYCLOAK; } @@ -163,28 +125,6 @@ private String tryToGetProviderName(String authServerUrl) { return null; } - private JsonObject discoverMetadata(String authServerUrl) { - WebClient client = OidcDevServicesUtils.createWebClient(vertxInstance); - try { - String metadataUrl = authServerUrl + OidcConstants.WELL_KNOWN_CONFIGURATION; - LOG.infof("OIDC Dev Console: discovering the provider metadata at %s", metadataUrl); - - HttpResponse resp = client.getAbs(metadataUrl) - .putHeader(HttpHeaders.ACCEPT.toString(), "application/json").send().await().indefinitely(); - if (resp.statusCode() == 200) { - return resp.bodyAsJsonObject(); - } else { - LOG.errorf("OIDC metadata discovery failed: %s", resp.bodyAsString()); - return null; - } - } catch (Throwable t) { - LOG.infof("OIDC metadata can not be discovered: %s", t.toString()); - return null; - } finally { - client.close(); - } - } - private static String getConfigProperty(String name) { return ConfigProvider.getConfig().getValue(name, String.class); } @@ -195,7 +135,7 @@ private static boolean isOidcTenantEnabled() { private static boolean isDiscoveryEnabled(OidcTenantConfig providerConfig) { return ConfigProvider.getConfig().getOptionalValue(DISCOVERY_ENABLED_CONFIG_KEY, Boolean.class) - .orElse((providerConfig != null ? providerConfig.discoveryEnabled.orElse(true) : true)); + .orElse((providerConfig != null ? providerConfig.discoveryEnabled().orElse(true) : true)); } private static boolean getBooleanProperty(String name) { @@ -210,7 +150,7 @@ private static String getAuthServerUrl(OidcTenantConfig providerConfig) { try { return getConfigProperty(AUTH_SERVER_URL_CONFIG_KEY); } catch (Exception ex) { - return providerConfig != null ? providerConfig.authServerUrl.get() : null; + return providerConfig != null ? providerConfig.authServerUrl().get() : null; } } diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java index c8468c7f002e0..71072fadc006d 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/KeycloakDevUIProcessor.java @@ -72,7 +72,7 @@ void produceProviderComponent(Optional confi users, keycloakRealms, configProps.get().isContainerRestarted(), - httpConfiguration); + httpConfiguration, false, null); // use same card page so that both pages appear on the same card var keycloakAdminPageItem = new KeycloakAdminPageBuildItem(cardPageBuildItem); keycloakAdminPageProducer.produce(keycloakAdminPageItem); diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java index 2944e03ffa7ae..742677989272b 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevJsonRpcService.java @@ -2,8 +2,6 @@ import static io.quarkus.oidc.runtime.devui.OidcDevServicesUtils.getTokens; -import jakarta.annotation.PostConstruct; -import jakarta.annotation.PreDestroy; import jakarta.inject.Inject; import org.eclipse.microprofile.config.ConfigProvider; @@ -21,17 +19,8 @@ public class OidcDevJsonRpcService { @Inject OidcDevLoginObserver oidcDevTokensObserver; - private Vertx vertx; - - @PostConstruct - public void startup() { - vertx = Vertx.vertx(); - } - - @PreDestroy - public void shutdown() { - vertx.close(); - } + @Inject + Vertx vertx; @NonBlocking public OidcDevUiRuntimePropertiesDTO getProperties() { @@ -72,7 +61,7 @@ public Multi streamOidcLoginEvent() { return oidcDevTokensObserver.streamOidcLoginEvent(); } - public void hydrate(OidcDevUiRpcSvcPropertiesBean properties, HttpConfiguration httpConfiguration) { + void hydrate(OidcDevUiRpcSvcPropertiesBean properties, HttpConfiguration httpConfiguration) { this.props = properties; this.httpConfiguration = httpConfiguration; } diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java index f1b220e4d1610..7cefaff7013f0 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/devui/OidcDevUiRecorder.java @@ -4,17 +4,28 @@ import java.util.List; import java.util.Map; +import org.jboss.logging.Logger; + import io.quarkus.arc.runtime.BeanContainer; +import io.quarkus.oidc.common.runtime.OidcConstants; import io.quarkus.oidc.runtime.OidcConfig; import io.quarkus.runtime.RuntimeValue; import io.quarkus.runtime.annotations.Recorder; +import io.quarkus.vertx.core.runtime.VertxCoreRecorder; import io.quarkus.vertx.http.runtime.HttpConfiguration; import io.vertx.core.Handler; +import io.vertx.core.http.HttpHeaders; +import io.vertx.core.json.JsonObject; import io.vertx.ext.web.RoutingContext; +import io.vertx.mutiny.core.buffer.Buffer; +import io.vertx.mutiny.ext.web.client.HttpResponse; +import io.vertx.mutiny.ext.web.client.WebClient; @Recorder public class OidcDevUiRecorder { + private static final Logger LOG = Logger.getLogger(OidcDevUiRecorder.class); + private final RuntimeValue oidcConfigRuntimeValue; public OidcDevUiRecorder(RuntimeValue oidcConfigRuntimeValue) { @@ -31,8 +42,18 @@ public RuntimeValue getRpcServiceProperties(Strin String logoutUrl, Duration webClientTimeout, Map> grantOptions, Map oidcUsers, String oidcProviderName, String oidcApplicationType, String oidcGrantType, boolean introspectionIsAvailable, String keycloakAdminUrl, List keycloakRealms, boolean swaggerIsAvailable, - boolean graphqlIsAvailable, String swaggerUiPath, String graphqlUiPath, boolean alwaysLogoutUserInDevUiOnReload) { - + boolean graphqlIsAvailable, String swaggerUiPath, String graphqlUiPath, boolean alwaysLogoutUserInDevUiOnReload, + boolean discoverMetadata, String authServerUrl) { + if (discoverMetadata) { + JsonObject metadata = discoverMetadata(authServerUrl); + if (metadata != null) { + authorizationUrl = metadata.getString("authorization_endpoint"); + tokenUrl = metadata.getString("token_endpoint"); + logoutUrl = metadata.getString("end_session_endpoint"); + introspectionIsAvailable = metadata.containsKey("introspection_endpoint") + || metadata.containsKey("userinfo_endpoint"); + } + } return new RuntimeValue( new OidcDevUiRpcSvcPropertiesBean(authorizationUrl, tokenUrl, logoutUrl, webClientTimeout, grantOptions, oidcUsers, oidcProviderName, oidcApplicationType, oidcGrantType, @@ -48,4 +69,25 @@ public Handler logoutHandler() { return new OidcDevSessionLogoutHandler(); } + private static JsonObject discoverMetadata(String authServerUrl) { + WebClient client = OidcDevServicesUtils.createWebClient(VertxCoreRecorder.getVertx().get()); + try { + String metadataUrl = authServerUrl + OidcConstants.WELL_KNOWN_CONFIGURATION; + LOG.infof("OIDC Dev Console: discovering the provider metadata at %s", metadataUrl); + + HttpResponse resp = client.getAbs(metadataUrl) + .putHeader(HttpHeaders.ACCEPT.toString(), "application/json").send().await().indefinitely(); + if (resp.statusCode() == 200) { + return resp.bodyAsJsonObject(); + } else { + LOG.errorf("OIDC metadata discovery failed: %s", resp.bodyAsString()); + return null; + } + } catch (Throwable t) { + LOG.infof("OIDC metadata can not be discovered: %s", t.toString()); + return null; + } finally { + client.close(); + } + } } diff --git a/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java index 0529704d89240..0f3371019ff86 100644 --- a/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java +++ b/integration-tests/oidc-mtls/src/test/java/io/quarkus/it/oidc/OidcMtlsTest.java @@ -10,6 +10,8 @@ import java.nio.file.Path; import java.nio.file.Paths; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import io.quarkus.oidc.common.runtime.OidcConstants; @@ -37,96 +39,94 @@ public class OidcMtlsTest { @TestHTTPResource(tls = true) URL url; - @Test - public void testMtlsJwt() throws Exception { - Vertx vertx = Vertx.vertx(); - try { - WebClientOptions options = createWebClientOptions(); - WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); - - // HTTP 200 - HttpResponse resp = webClient.get("/service/mtls-jwt") - .putHeader("Authorization", - OidcConstants.BEARER_SCHEME + " " + getAccessToken("backend-service", null, "alice")) - .send().await() - .indefinitely(); - assertEquals(200, resp.statusCode()); - String name = resp.bodyAsString(); - assertEquals("Identities: CN=backend-service, alice;" - + " Client: backend-service;" - + " JWT cert thumbprint: true, introspection cert thumbprint: false", name); - - // HTTP 401, invalid token - resp = webClient.get("/service/mtls-jwt") - .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + "123") - .send().await() - .indefinitely(); - assertEquals(401, resp.statusCode()); - } finally { + private static Vertx vertx; + + @BeforeAll + public static void createVertx() { + vertx = Vertx.vertx(); + } + + @AfterAll + public static void closeVertx() { + if (vertx != null) { vertx.close(); } } + @Test + public void testMtlsJwt() throws Exception { + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); + + // HTTP 200 + HttpResponse resp = webClient.get("/service/mtls-jwt") + .putHeader("Authorization", + OidcConstants.BEARER_SCHEME + " " + getAccessToken("backend-service", null, "alice")) + .send().await() + .indefinitely(); + assertEquals(200, resp.statusCode()); + String name = resp.bodyAsString(); + assertEquals("Identities: CN=backend-service, alice;" + + " Client: backend-service;" + + " JWT cert thumbprint: true, introspection cert thumbprint: false", name); + + // HTTP 401, invalid token + resp = webClient.get("/service/mtls-jwt") + .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + "123") + .send().await() + .indefinitely(); + assertEquals(401, resp.statusCode()); + } + @Test public void testMtlsIntrospection() throws Exception { - Vertx vertx = Vertx.vertx(); - try { - WebClientOptions options = createWebClientOptions(); - WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); - - // HTTP 200 - HttpResponse resp = webClient.get("/service/mtls-introspection") - .putHeader("Authorization", - OidcConstants.BEARER_SCHEME + " " + getAccessToken("backend-service", null, "alice")) - .send().await() - .indefinitely(); - assertEquals(200, resp.statusCode()); - String name = resp.bodyAsString(); - assertEquals("Identities: CN=backend-service, alice;" - + " Client: backend-service;" - + " JWT cert thumbprint: false, introspection cert thumbprint: true", name); - - // HTTP 401, invalid token - resp = webClient.get("/service/mtls-introspection") - .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + "123") - .send().await() - .indefinitely(); - assertEquals(401, resp.statusCode()); - } finally { - vertx.close(); - } + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); + + // HTTP 200 + HttpResponse resp = webClient.get("/service/mtls-introspection") + .putHeader("Authorization", + OidcConstants.BEARER_SCHEME + " " + getAccessToken("backend-service", null, "alice")) + .send().await() + .indefinitely(); + assertEquals(200, resp.statusCode()); + String name = resp.bodyAsString(); + assertEquals("Identities: CN=backend-service, alice;" + + " Client: backend-service;" + + " JWT cert thumbprint: false, introspection cert thumbprint: true", name); + + // HTTP 401, invalid token + resp = webClient.get("/service/mtls-introspection") + .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + "123") + .send().await() + .indefinitely(); + assertEquals(401, resp.statusCode()); } @Test public void testMtlsClientWithSecret() throws Exception { - Vertx vertx = Vertx.vertx(); - try { - WebClientOptions options = createWebClientOptions(); - WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); - - String accessToken = getAccessToken("backend-client-with-secret", "secret", "alice"); - // HTTP 200 - HttpResponse resp = webClient.get("/service/mtls-client-with-secret") - .putHeader("Authorization", - OidcConstants.BEARER_SCHEME + " " + accessToken) - .send().await() - .indefinitely(); - assertEquals(200, resp.statusCode()); - String name = resp.bodyAsString(); - assertEquals("Identities: CN=backend-service, alice;" - + " Client: backend-client-with-secret;" - + " JWT cert thumbprint: false, introspection cert thumbprint: false", name); - - // HTTP 401, token is valid but it is not certificate bound - resp = webClient.get("/service/mtls-jwt") - .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + accessToken) - .send().await() - .indefinitely(); - assertEquals(401, resp.statusCode()); - - } finally { - vertx.close(); - } + WebClientOptions options = createWebClientOptions(); + WebClient webClient = WebClient.create(new io.vertx.mutiny.core.Vertx(vertx), options); + + String accessToken = getAccessToken("backend-client-with-secret", "secret", "alice"); + // HTTP 200 + HttpResponse resp = webClient.get("/service/mtls-client-with-secret") + .putHeader("Authorization", + OidcConstants.BEARER_SCHEME + " " + accessToken) + .send().await() + .indefinitely(); + assertEquals(200, resp.statusCode()); + String name = resp.bodyAsString(); + assertEquals("Identities: CN=backend-service, alice;" + + " Client: backend-client-with-secret;" + + " JWT cert thumbprint: false, introspection cert thumbprint: false", name); + + // HTTP 401, token is valid but it is not certificate bound + resp = webClient.get("/service/mtls-jwt") + .putHeader("Authorization", OidcConstants.BEARER_SCHEME + " " + accessToken) + .send().await() + .indefinitely(); + assertEquals(401, resp.statusCode()); } private String getAccessToken(String clientName, String clientSecret, String userName) { diff --git a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/client/OidcTestClient.java b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/client/OidcTestClient.java index d0964004442a3..a857600ab2b50 100644 --- a/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/client/OidcTestClient.java +++ b/test-framework/oidc-server/src/main/java/io/quarkus/test/oidc/client/OidcTestClient.java @@ -9,6 +9,8 @@ import org.eclipse.microprofile.config.ConfigProvider; +import io.quarkus.arc.Arc; +import io.quarkus.arc.ArcContainer; import io.vertx.core.MultiMap; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; @@ -22,8 +24,8 @@ public class OidcTestClient { private final static String CLIENT_ID_PROP = "quarkus.oidc.client-id"; private final static String CLIENT_SECRET_PROP = "quarkus.oidc.credentials.secret"; - Vertx vertx = Vertx.vertx(); - WebClient client = WebClient.create(vertx); + private volatile Vertx vertx = null; + private volatile WebClient client = null; private String authServerUrl; private String tokenUrl; @@ -121,7 +123,7 @@ private String getAccessTokenInternal(MultiMap requestMap, Map e requestMap = requestMap.addAll(extraProps); } - var result = client.postAbs(getTokenUrl()) + var result = getClient().postAbs(getTokenUrl()) .putHeader("Content-Type", "application/x-www-form-urlencoded") .sendBuffer(encodeForm(requestMap)); await().atMost(REQUEST_TIMEOUT).until(result::isComplete); @@ -153,7 +155,7 @@ public String getAuthServerUrl() { public String getTokenUrl() { if (tokenUrl == null) { getAuthServerUrl(); - var result = client.getAbs(authServerUrl + "/.well-known/openid-configuration") + var result = getClient().getAbs(authServerUrl + "/.well-known/openid-configuration") .send(); await().atMost(REQUEST_TIMEOUT).until(result::isComplete); tokenUrl = result.result().bodyAsJsonObject().getString("token_endpoint"); @@ -191,6 +193,27 @@ private static String urlEncode(String value) { } } + private WebClient getClient() { + if (client == null) { + client = WebClient.create(getVertx()); + } + return client; + } + + private Vertx getVertx() { + final ArcContainer container = Arc.container(); + if (container != null && container.isRunning()) { + var managedVertx = container.instance(Vertx.class).orElse(null); + if (managedVertx != null) { + return managedVertx; + } + } + if (vertx == null) { + vertx = Vertx.vertx(); + } + return vertx; + } + public void close() { if (client != null) { client.close();