From 561a063859860da7913735a73e6a551a643c755c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 14 Dec 2022 18:18:08 +0100 Subject: [PATCH] Enhance OidcClientFilter to select named OIDC clients closes: #16397 --- ...urity-openid-connect-client-reference.adoc | 36 +++++++ .../NamedOidcClientFilterBuildItem.java | 17 ++++ .../deployment/OidcClientFilterBuildStep.java | 95 +++++++++++++++++- .../ConfigPropertyOidcClientResource.java | 21 ++++ .../NamedOidcClientFilterDevModeTest.java | 45 +++++++++ .../filter/NamedOidcClientResource.java | 21 ++++ .../filter/OidcClientFilterDevModeTest.java | 13 ++- ...sourceServiceConfigPropertyOidcClient.java | 15 +++ ...otectedResourceServiceNamedOidcClient.java | 15 +++ ...cation-named-oidc-client-filter.properties | 23 +++++ .../application-oidc-client-filter.properties | 9 ++ .../filter/OidcClientRequestFilter.java | 34 +------ .../AbstractOidcClientRequestFilter.java | 40 ++++++++ .../runtime/OidcClientFilterConfig.java | 3 +- .../deployment/pom.xml | 56 +++++++++++ .../OidcClientReactiveFilterBuildStep.java | 36 ++++++- .../NamedOidcClientFilterDevModeTest.java | 46 +++++++++ .../reactive/filter/OidcClientResource.java | 31 ++++++ .../reactive/filter/ProtectedResource.java | 24 +++++ ...edResourceServiceAnnotationOidcClient.java | 17 ++++ ...sourceServiceConfigPropertyOidcClient.java | 17 ++++ ...ion-oidc-client-reactive-filter.properties | 23 +++++ .../OidcClientRequestReactiveFilter.java | 57 +---------- ...stractOidcClientRequestReactiveFilter.java | 54 ++++++++++ .../OidcClientReactiveFilterConfig.java | 3 +- .../deployment/OidcClientBuildStep.java | 6 +- .../OidcClientFilterDeploymentHelper.java | 98 +++++++++++++++++++ .../oidc/client/filter/OidcClientFilter.java | 6 ++ .../runtime/AbstractTokensProducer.java | 20 ++-- .../RestClientPredicateProviderBuildItem.java | 36 +++++++ .../deployment/RestClientProcessor.java | 44 ++++++--- .../restclient/runtime/RestClientBase.java | 14 +-- .../quarkus/it/keycloak/FrontendResource.java | 11 +++ .../ProtectedResourceServiceNamedFilter.java | 21 ++++ .../src/main/resources/application.properties | 8 ++ .../quarkus/it/keycloak/OidcClientTest.java | 9 ++ .../quarkus/it/keycloak/FrontendResource.java | 10 ++ ...edResourceServiceNonDefaultOidcClient.java | 18 ++++ .../src/main/resources/application.properties | 8 ++ .../KeycloakRealmResourceManager.java | 1 + .../quarkus/it/keycloak/OidcClientTest.java | 8 ++ 41 files changed, 942 insertions(+), 127 deletions(-) create mode 100644 extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/NamedOidcClientFilterBuildItem.java create mode 100644 extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ConfigPropertyOidcClientResource.java create mode 100644 extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientFilterDevModeTest.java create mode 100644 extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientResource.java create mode 100644 extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceConfigPropertyOidcClient.java create mode 100644 extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceNamedOidcClient.java create mode 100644 extensions/oidc-client-filter/deployment/src/test/resources/application-named-oidc-client-filter.properties create mode 100644 extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java create mode 100644 extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/NamedOidcClientFilterDevModeTest.java create mode 100644 extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/OidcClientResource.java create mode 100644 extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResource.java create mode 100644 extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceAnnotationOidcClient.java create mode 100644 extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceConfigPropertyOidcClient.java create mode 100644 extensions/oidc-client-reactive-filter/deployment/src/test/resources/application-oidc-client-reactive-filter.properties create mode 100644 extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java create mode 100644 extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientFilterDeploymentHelper.java create mode 100644 extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientPredicateProviderBuildItem.java create mode 100644 integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNamedFilter.java create mode 100644 integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNonDefaultOidcClient.java diff --git a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc index 86c625ca2b5baf..1d648704ed2a1e 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client-reference.adoc @@ -208,6 +208,7 @@ public class OidcClientResource { } ---- +[[use-oidc-clients]] === Use OidcClients `io.quarkus.oidc.client.OidcClients` is a container of ``OidcClient``s - it includes a default `OidcClient` and named clients which can be configured like this: @@ -419,7 +420,25 @@ public interface ProtectedResourceService { ---- `OidcClientRequestReactiveFilter` uses a default `OidcClient` by default. A named `OidcClient` can be selected with a `quarkus.oidc-client-reactive-filter.client-name` configuration property. +You can also select `OidcClient` by setting `value` attribute of the `@OidcClientFilter` annotation. The client name set through annotation has higher priority than the `quarkus.oidc-client-reactive-filter.client-name` configuration property. +For example, given <> `jwt-secret` named OIDC client declaration, you can refer to this client like this: +[source,java] +---- +import org.eclipse.microprofile.rest.client.annotation.RegisterProvider; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import io.quarkus.oidc.client.filter.OidcClientFilter; +import io.smallrye.mutiny.Uni; + +@RegisterRestClient +@OidcClientFilter("jwt-secret") +@Path("/") +public interface ProtectedResourceService { + + @GET + Uni getUserName(); +} +---- [[oidc-client-filter]] === Use OidcClient in RestClient ClientFilter @@ -478,6 +497,23 @@ public interface ProtectedResourceService { Alternatively, `OidcClientRequestFilter` can be registered automatically with all MP Rest or JAX-RS clients if `quarkus.oidc-client-filter.register-filter=true` property is set. `OidcClientRequestFilter` uses a default `OidcClient` by default. A named `OidcClient` can be selected with a `quarkus.oidc-client-filter.client-name` configuration property. +You can also select `OidcClient` by setting `value` attribute of the `@OidcClientFilter` annotation. The client name set through annotation has higher priority than the `quarkus.oidc-client-filter.client-name` configuration property. +For example, given <> `jwt-secret` named OIDC client declaration, you can refer to this client like this: + +[source,java] +---- +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; +import io.quarkus.oidc.client.filter.OidcClientFilter; + +@RegisterRestClient +@OidcClientFilter("jwt-secret") +@Path("/") +public interface ProtectedResourceService { + + @GET + String getUserName(); +} +---- === Use Custom RestClient ClientFilter diff --git a/extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/NamedOidcClientFilterBuildItem.java b/extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/NamedOidcClientFilterBuildItem.java new file mode 100644 index 00000000000000..10ef1343f1a4b5 --- /dev/null +++ b/extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/NamedOidcClientFilterBuildItem.java @@ -0,0 +1,17 @@ +package io.quarkus.oidc.client.filter.deployment; + +import java.util.Set; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Contains a list of all Rest clients annotated with @OidcClientFilter("someClientName"). + */ +public final class NamedOidcClientFilterBuildItem extends SimpleBuildItem { + + final Set namedFilterClientClasses; + + NamedOidcClientFilterBuildItem(Set namedFilterClientClasses) { + this.namedFilterClientClasses = Set.copyOf(namedFilterClientClasses); + } +} diff --git a/extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/OidcClientFilterBuildStep.java b/extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/OidcClientFilterBuildStep.java index 5d1ce05cdbd40f..ddf729ac033ae0 100644 --- a/extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/OidcClientFilterBuildStep.java +++ b/extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/OidcClientFilterBuildStep.java @@ -1,17 +1,29 @@ package io.quarkus.oidc.client.filter.deployment; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Predicate; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; +import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.oidc.client.deployment.OidcClientBuildStep.IsEnabled; +import io.quarkus.oidc.client.deployment.OidcClientFilterDeploymentHelper; import io.quarkus.oidc.client.filter.OidcClientFilter; import io.quarkus.oidc.client.filter.OidcClientRequestFilter; +import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter; import io.quarkus.oidc.client.filter.runtime.OidcClientFilterConfig; import io.quarkus.restclient.deployment.RestClientAnnotationProviderBuildItem; +import io.quarkus.restclient.deployment.RestClientPredicateProviderBuildItem; import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem; @BuildSteps(onlyIf = IsEnabled.class) @@ -24,15 +36,92 @@ public class OidcClientFilterBuildStep { @BuildStep void registerProvider(BuildProducer additionalBeans, BuildProducer reflectiveClass, + NamedOidcClientFilterBuildItem namedOidcClientFilterBuildItem, BuildProducer jaxrsProviders, + BuildProducer restPredicateProvider, BuildProducer restAnnotationProvider) { + additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(OidcClientRequestFilter.class)); reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, OidcClientRequestFilter.class)); + final Set namedFilterClientClasses = namedOidcClientFilterBuildItem.namedFilterClientClasses; + + // register default request filter provider against the rest of the clients (client != namedFilterClientClasses) if (config.registerFilter) { - jaxrsProviders.produce(new ResteasyJaxrsProviderBuildItem(OidcClientRequestFilter.class.getName())); + if (namedFilterClientClasses.isEmpty()) { + // register default request filter as global rest client provider + jaxrsProviders.produce(new ResteasyJaxrsProviderBuildItem(OidcClientRequestFilter.class.getName())); + } else { + // register all clients without @OidcClientFilter("clientName") + restPredicateProvider + .produce(new RestClientPredicateProviderBuildItem(OidcClientRequestFilter.class.getName(), + new Predicate() { + // test whether the provider should be added restClientClassInfo + @Override + public boolean test(ClassInfo restClientClassInfo) { + // do not register default request filter as provider against Rest client with named filter + return !namedFilterClientClasses.contains(restClientClassInfo.name().toString()); + } + })); + } } else { - restAnnotationProvider.produce(new RestClientAnnotationProviderBuildItem(OIDC_CLIENT_FILTER, - OidcClientRequestFilter.class)); + if (namedFilterClientClasses.isEmpty()) { + // register default request filter against all the Rest clients annotated with @OidcClientFilter + restAnnotationProvider.produce(new RestClientAnnotationProviderBuildItem(OIDC_CLIENT_FILTER, + OidcClientRequestFilter.class)); + } else { + // register default request filter against Rest client annotated with @OidcClientFilter without named ones + restPredicateProvider + .produce(new RestClientPredicateProviderBuildItem(OidcClientRequestFilter.class.getName(), + new Predicate() { + // test whether the provider should be added restClientClassInfo + @Override + public boolean test(ClassInfo restClientClassInfo) { + // do not register default request filter as provider against Rest client with named filter + return restClientClassInfo.hasAnnotation(OIDC_CLIENT_FILTER) + && !namedFilterClientClasses.contains(restClientClassInfo.name().toString()); + } + })); + } + } + } + + @BuildStep + NamedOidcClientFilterBuildItem registerNamedProviders(BuildProducer reflectiveClass, + CombinedIndexBuildItem indexBuildItem, + BuildProducer restPredicateProvider, + BuildProducer generatedBean) { + + // create and register named request filter for each @OidcClientFilter("clientName") + final var helper = new OidcClientFilterDeploymentHelper<>(AbstractOidcClientRequestFilter.class, generatedBean); + Collection instances = indexBuildItem.getIndex().getAnnotations(OIDC_CLIENT_FILTER); + final Set namedFilterClientClasses = new HashSet<>(); + for (AnnotationInstance instance : instances) { + // get client name from annotation @OidcClientFilter("clientName") + final String clientName = OidcClientFilterDeploymentHelper.getClientName(instance); + // do not create & register named filter for the OidcClient registered through configuration property + // as default request filter got it covered + if (clientName != null && !clientName.equals(config.clientName.orElse(null))) { + + // create named filter class for named OidcClient + // we generate exactly one custom filter for each named client specified through annotation + final var generatedProvider = helper.getOrCreateNamedTokensProducerFor(clientName); + final var targetRestClient = instance.target().asClass().name().toString(); + namedFilterClientClasses.add(targetRestClient); + + // register for reflection + reflectiveClass.produce(new ReflectiveClassBuildItem(true, true, true, true, generatedProvider)); + + // register named request filter provider against Rest client + restPredicateProvider.produce(new RestClientPredicateProviderBuildItem(generatedProvider, + new Predicate() { + // test whether the provider should be added restClientClassInfo + @Override + public boolean test(ClassInfo restClientClassInfo) { + return targetRestClient.equals(restClientClassInfo.name().toString()); + } + })); + } } + return new NamedOidcClientFilterBuildItem(namedFilterClientClasses); } } diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ConfigPropertyOidcClientResource.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ConfigPropertyOidcClientResource.java new file mode 100644 index 00000000000000..4ec506a74dd40e --- /dev/null +++ b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ConfigPropertyOidcClientResource.java @@ -0,0 +1,21 @@ +package io.quarkus.oidc.client.filter; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("/config-property-oidc-client") +public class ConfigPropertyOidcClientResource { + + @Inject + @RestClient + ProtectedResourceServiceConfigPropertyOidcClient protectedResourceServiceConfigPropertyOidcClient; + + @GET + @Path("user-name") + public String userName() { + return protectedResourceServiceConfigPropertyOidcClient.getUserName(); + } +} diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientFilterDevModeTest.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientFilterDevModeTest.java new file mode 100644 index 00000000000000..e4ec94d279172e --- /dev/null +++ b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientFilterDevModeTest.java @@ -0,0 +1,45 @@ +package io.quarkus.oidc.client.filter; + +import static org.hamcrest.Matchers.equalTo; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; +import io.restassured.RestAssured; + +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +public class NamedOidcClientFilterDevModeTest { + + private static final Class[] testClasses = { + ProtectedResource.class, + ProtectedResourceServiceNamedOidcClient.class, + ProtectedResourceServiceConfigPropertyOidcClient.class, + NamedOidcClientResource.class, + ConfigPropertyOidcClientResource.class + }; + + @RegisterExtension + static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(testClasses) + .addAsResource("application-named-oidc-client-filter.properties", "application.properties")); + + @Test + public void testGerUserConfigPropertyAndAnnotation() { + // OidcClient selected via @OidcClient("clientName") + RestAssured.when().get("/named-oidc-client/user-name") + .then() + .statusCode(200) + .body(equalTo("jdoe")); + + // OidcClient selected via `quarkus.oidc-client-filter.client-name=config-property` + RestAssured.when().get("/config-property-oidc-client/user-name") + .then() + .statusCode(200) + .body(equalTo("alice")); + } + +} diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientResource.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientResource.java new file mode 100644 index 00000000000000..5419cddb7598cc --- /dev/null +++ b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/NamedOidcClientResource.java @@ -0,0 +1,21 @@ +package io.quarkus.oidc.client.filter; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("/named-oidc-client") +public class NamedOidcClientResource { + + @Inject + @RestClient + ProtectedResourceServiceNamedOidcClient protectedResourceServiceNamedOidcClient; + + @GET + @Path("user-name") + public String userName() { + return protectedResourceServiceNamedOidcClient.getUserName(); + } +} diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java index 68e1606c090872..97cbbb8d7868c0 100644 --- a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java +++ b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterDevModeTest.java @@ -26,10 +26,12 @@ @QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) public class OidcClientFilterDevModeTest { - private static Class[] testClasses = { + private static final Class[] testClasses = { FrontendResource.class, ProtectedResource.class, - ProtectedResourceService.class + ProtectedResourceService.class, + ProtectedResourceServiceNamedOidcClient.class, + NamedOidcClientResource.class }; @RegisterExtension @@ -60,6 +62,13 @@ public void testGetUserName() { .statusCode(200) .body(equalTo("alice")); checkLog(); + + // here we test that user can optionally select named OidcClient like this @OidcClient("clientName") + // even though 'quarkus.oidc-client-filter.register-filter' is enabled + RestAssured.when().get("/named-oidc-client/user-name") + .then() + .statusCode(200) + .body(equalTo("jdoe")); } private void checkLog() { diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceConfigPropertyOidcClient.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceConfigPropertyOidcClient.java new file mode 100644 index 00000000000000..929f35e34e3d41 --- /dev/null +++ b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceConfigPropertyOidcClient.java @@ -0,0 +1,15 @@ +package io.quarkus.oidc.client.filter; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@OidcClientFilter +@RegisterRestClient +@Path("/") +public interface ProtectedResourceServiceConfigPropertyOidcClient { + + @GET + String getUserName(); +} diff --git a/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceNamedOidcClient.java b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceNamedOidcClient.java new file mode 100644 index 00000000000000..efb484c0630d86 --- /dev/null +++ b/extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/ProtectedResourceServiceNamedOidcClient.java @@ -0,0 +1,15 @@ +package io.quarkus.oidc.client.filter; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@OidcClientFilter("named") +@RegisterRestClient +@Path("/") +public interface ProtectedResourceServiceNamedOidcClient { + + @GET + String getUserName(); +} diff --git a/extensions/oidc-client-filter/deployment/src/test/resources/application-named-oidc-client-filter.properties b/extensions/oidc-client-filter/deployment/src/test/resources/application-named-oidc-client-filter.properties new file mode 100644 index 00000000000000..9cb7a6ead40646 --- /dev/null +++ b/extensions/oidc-client-filter/deployment/src/test/resources/application-named-oidc-client-filter.properties @@ -0,0 +1,23 @@ +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ +quarkus.oidc.client-id=quarkus-service-app +quarkus.oidc.credentials.secret=secret + +quarkus.oidc-client-filter.client-name=config-property +quarkus.oidc-client.config-property.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.config-property.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.config-property.credentials.client-secret.value=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.config-property.credentials.client-secret.method=POST +quarkus.oidc-client.config-property.grant.type=password +quarkus.oidc-client.config-property.grant-options.password.username=alice +quarkus.oidc-client.config-property.grant-options.password.password=alice + +quarkus.oidc-client.named.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.named.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.named.credentials.client-secret.value=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.named.credentials.client-secret.method=POST +quarkus.oidc-client.named.grant.type=password +quarkus.oidc-client.named.grant-options.password.username=jdoe +quarkus.oidc-client.named.grant-options.password.password=jdoe + +io.quarkus.oidc.client.filter.ProtectedResourceServiceNamedOidcClient/mp-rest/url=http://localhost:8080/protected +io.quarkus.oidc.client.filter.ProtectedResourceServiceConfigPropertyOidcClient/mp-rest/url=http://localhost:8080/protected \ No newline at end of file diff --git a/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties b/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties index 830a0ef048dfd6..e633ad9ba461c8 100644 --- a/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties +++ b/extensions/oidc-client-filter/deployment/src/test/resources/application-oidc-client-filter.properties @@ -10,10 +10,19 @@ quarkus.oidc-client.grant.type=password quarkus.oidc-client.grant-options.password.username=alice quarkus.oidc-client.grant-options.password.password=alice +quarkus.oidc-client.named.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.named.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.named.credentials.client-secret.value=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.named.credentials.client-secret.method=POST +quarkus.oidc-client.named.grant.type=password +quarkus.oidc-client.named.grant-options.password.username=jdoe +quarkus.oidc-client.named.grant-options.password.password=jdoe + #quarkus.oidc-client-filter.register-filter=true quarkus.oidc-client.refresh-token-time-skew=5S io.quarkus.oidc.client.filter.ProtectedResourceService/mp-rest/url=http://localhost:8080/protected +io.quarkus.oidc.client.filter.ProtectedResourceServiceNamedOidcClient/mp-rest/url=http://localhost:8080/protected quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".min-level=TRACE quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".level=TRACE quarkus.log.file.enable=true diff --git a/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientRequestFilter.java b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientRequestFilter.java index 5da589248d305c..e1a3fa9708094f 100644 --- a/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientRequestFilter.java +++ b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientRequestFilter.java @@ -1,52 +1,24 @@ package io.quarkus.oidc.client.filter; -import java.io.IOException; import java.util.Optional; import javax.annotation.Priority; import javax.inject.Inject; import javax.inject.Singleton; import javax.ws.rs.Priorities; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; import javax.ws.rs.ext.Provider; -import org.jboss.logging.Logger; - +import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter; import io.quarkus.oidc.client.filter.runtime.OidcClientFilterConfig; -import io.quarkus.oidc.client.runtime.AbstractTokensProducer; -import io.quarkus.oidc.client.runtime.DisabledOidcClientException; -import io.quarkus.oidc.common.runtime.OidcConstants; @Provider @Singleton @Priority(Priorities.AUTHENTICATION) -public class OidcClientRequestFilter extends AbstractTokensProducer implements ClientRequestFilter { - private static final Logger LOG = Logger.getLogger(OidcClientRequestFilter.class); - private static final String BEARER_SCHEME_WITH_SPACE = OidcConstants.BEARER_SCHEME + " "; +public class OidcClientRequestFilter extends AbstractOidcClientRequestFilter { + @Inject OidcClientFilterConfig oidcClientFilterConfig; - @Override - public void filter(ClientRequestContext requestContext) throws IOException { - try { - final String accessToken = getAccessToken(); - requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_SCHEME_WITH_SPACE + accessToken); - } catch (DisabledOidcClientException ex) { - requestContext.abortWith(Response.status(500).build()); - } catch (Exception ex) { - LOG.debugf("Access token is not available, aborting the request with HTTP 401 error: %s", ex.getMessage()); - requestContext.abortWith(Response.status(401).build()); - } - } - - private String getAccessToken() { - // It should be reactive when run with Resteasy Reactive - return awaitTokens().getAccessToken(); - } - protected Optional clientId() { return oidcClientFilterConfig.clientName; } diff --git a/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java new file mode 100644 index 00000000000000..35de8080216d09 --- /dev/null +++ b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java @@ -0,0 +1,40 @@ +package io.quarkus.oidc.client.filter.runtime; + +import java.io.IOException; + +import javax.ws.rs.client.ClientRequestContext; +import javax.ws.rs.client.ClientRequestFilter; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.jboss.logging.Logger; + +import io.quarkus.oidc.client.runtime.AbstractTokensProducer; +import io.quarkus.oidc.client.runtime.DisabledOidcClientException; +import io.quarkus.oidc.common.runtime.OidcConstants; + +public class AbstractOidcClientRequestFilter extends AbstractTokensProducer implements ClientRequestFilter { + private static final Logger LOG = Logger.getLogger(AbstractOidcClientRequestFilter.class); + private static final String BEARER_SCHEME_WITH_SPACE = OidcConstants.BEARER_SCHEME + " "; + + public AbstractOidcClientRequestFilter() { + } + + @Override + public void filter(ClientRequestContext requestContext) throws IOException { + try { + final String accessToken = getAccessToken(); + requestContext.getHeaders().add(HttpHeaders.AUTHORIZATION, BEARER_SCHEME_WITH_SPACE + accessToken); + } catch (DisabledOidcClientException ex) { + requestContext.abortWith(Response.status(500).build()); + } catch (Exception ex) { + LOG.debugf("Access token is not available, aborting the request with HTTP 401 error: %s", ex.getMessage()); + requestContext.abortWith(Response.status(401).build()); + } + } + + private String getAccessToken() { + return awaitTokens().getAccessToken(); + } + +} diff --git a/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/OidcClientFilterConfig.java b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/OidcClientFilterConfig.java index f491904fc21698..37ab2a7a03768c 100644 --- a/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/OidcClientFilterConfig.java +++ b/extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/OidcClientFilterConfig.java @@ -16,7 +16,8 @@ public class OidcClientFilterConfig { public boolean registerFilter; /** - * Name of the configured OidcClient. + * Name of the configured OidcClient used by the OidcClientRequestFilter. You can override this configuration for + * individual MP RestClient with the `io.quarkus.oidc.client.filter.OidcClientFilter` annotation. */ @ConfigItem public Optional clientName; diff --git a/extensions/oidc-client-reactive-filter/deployment/pom.xml b/extensions/oidc-client-reactive-filter/deployment/pom.xml index 171436bc7a5c31..b547995beab704 100644 --- a/extensions/oidc-client-reactive-filter/deployment/pom.xml +++ b/extensions/oidc-client-reactive-filter/deployment/pom.xml @@ -26,6 +26,38 @@ io.quarkus quarkus-rest-client-reactive-deployment + + + io.quarkus + quarkus-junit5-internal + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-test-keycloak-server + test + + + junit + junit + + + + + io.quarkus + quarkus-oidc-deployment + test + + + io.quarkus + quarkus-resteasy-reactive-deployment + test + @@ -56,4 +88,28 @@ + + + test-keycloak + + + test-containers + + + + + + maven-surefire-plugin + + false + + ${keycloak.docker.legacy.image} + false + + + + + + + diff --git a/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java b/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java index 1b8579b71eadae..6bd86742464598 100644 --- a/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java +++ b/extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java @@ -3,12 +3,15 @@ import java.util.Collection; import java.util.List; +import javax.ws.rs.Priorities; + import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.DotName; import org.jboss.jandex.Type; import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; import io.quarkus.deployment.annotations.BuildSteps; @@ -16,8 +19,11 @@ import io.quarkus.deployment.builditem.CombinedIndexBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.oidc.client.deployment.OidcClientBuildStep.IsEnabled; +import io.quarkus.oidc.client.deployment.OidcClientFilterDeploymentHelper; import io.quarkus.oidc.client.filter.OidcClientFilter; import io.quarkus.oidc.client.reactive.filter.OidcClientRequestReactiveFilter; +import io.quarkus.oidc.client.reactive.filter.runtime.AbstractOidcClientRequestReactiveFilter; +import io.quarkus.oidc.client.reactive.filter.runtime.OidcClientReactiveFilterConfig; import io.quarkus.rest.client.reactive.deployment.DotNames; import io.quarkus.rest.client.reactive.deployment.RegisterProviderAnnotationInstanceBuildItem; @@ -27,20 +33,44 @@ public class OidcClientReactiveFilterBuildStep { private static final DotName OIDC_CLIENT_FILTER = DotName.createSimple(OidcClientFilter.class.getName()); private static final DotName OIDC_CLIENT_REQUEST_REACTIVE_FILTER = DotName .createSimple(OidcClientRequestReactiveFilter.class.getName()); + OidcClientReactiveFilterConfig oidcClientReactiveFilterConfig; // we simply pretend that @OidcClientFilter means @RegisterProvider(OidcClientRequestReactiveFilter.class) @BuildStep - void oidcClientFilterSupport(CombinedIndexBuildItem indexBuildItem, + void oidcClientFilterSupport(CombinedIndexBuildItem indexBuildItem, BuildProducer generatedBean, BuildProducer producer) { + final var helper = new OidcClientFilterDeploymentHelper<>(AbstractOidcClientRequestReactiveFilter.class, generatedBean); + final var defaultFilter = oidcClientReactiveFilterConfig.clientName.isPresent() + ? helper.getOrCreateFilter(oidcClientReactiveFilterConfig.clientName.get()) + : OIDC_CLIENT_REQUEST_REACTIVE_FILTER; + Collection instances = indexBuildItem.getIndex().getAnnotations(OIDC_CLIENT_FILTER); for (AnnotationInstance instance : instances) { + + // get client name from annotation @OidcClientFilter("clientName") + final String clientName = OidcClientFilterDeploymentHelper.getClientName(instance); + final AnnotationValue valueAttr; + if (clientName != null) { + // create and use custom filter for named OidcClient + // we generate exactly one custom filter for each named client specified through annotation + valueAttr = createClassValue(helper.getOrCreateFilter(clientName)); + } else { + // use default filter for either default OidcClient or the client configured with config properties + valueAttr = createClassValue(defaultFilter); + } + + final AnnotationValue priorityAttr = AnnotationValue.createIntegerValue("priority", Priorities.AUTHENTICATION); String targetClass = instance.target().asClass().name().toString(); producer.produce(new RegisterProviderAnnotationInstanceBuildItem(targetClass, AnnotationInstance.create( - DotNames.REGISTER_PROVIDER, instance.target(), List.of(AnnotationValue.createClassValue("value", - Type.create(OIDC_CLIENT_REQUEST_REACTIVE_FILTER, org.jboss.jandex.Type.Kind.CLASS)))))); + DotNames.REGISTER_PROVIDER, instance.target(), List.of(valueAttr, priorityAttr)))); } } + private AnnotationValue createClassValue(DotName filter) { + return AnnotationValue.createClassValue("value", + Type.create(filter, Type.Kind.CLASS)); + } + @BuildStep void registerProvider(BuildProducer additionalBeans, BuildProducer reflectiveClass, diff --git a/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/NamedOidcClientFilterDevModeTest.java b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/NamedOidcClientFilterDevModeTest.java new file mode 100644 index 00000000000000..bb4a0c3f4cd90f --- /dev/null +++ b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/NamedOidcClientFilterDevModeTest.java @@ -0,0 +1,46 @@ +package io.quarkus.oidc.client.reactive.filter; + +import static org.hamcrest.Matchers.equalTo; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.test.QuarkusDevModeTest; +import io.quarkus.test.common.QuarkusTestResource; +import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; +import io.restassured.RestAssured; + +@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +public class NamedOidcClientFilterDevModeTest { + + private static final Class[] testClasses = { + ProtectedResource.class, + ProtectedResourceServiceAnnotationOidcClient.class, + ProtectedResourceServiceConfigPropertyOidcClient.class, + OidcClientResource.class + }; + + @RegisterExtension + static final QuarkusDevModeTest test = new QuarkusDevModeTest() + .withApplicationRoot((jar) -> jar + .addClasses(testClasses) + .addAsResource("application-oidc-client-reactive-filter.properties", "application.properties")); + + @Test + public void testGerUserConfigPropertyAndAnnotation() { + // test OidcClientFilter with OidcClient selected via annotation or config-property + + // OidcClient selected via @OidcClient("clientName") + RestAssured.when().get("/oidc-client/annotation/user-name") + .then() + .statusCode(200) + .body(equalTo("jdoe")); + + // OidcClient selected via `quarkus.oidc-client-filter.client-name=config-property` + RestAssured.when().get("/oidc-client/config-property/user-name") + .then() + .statusCode(200) + .body(equalTo("alice")); + } + +} diff --git a/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/OidcClientResource.java b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/OidcClientResource.java new file mode 100644 index 00000000000000..e88bf2bf74db2d --- /dev/null +++ b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/OidcClientResource.java @@ -0,0 +1,31 @@ +package io.quarkus.oidc.client.reactive.filter; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RestClient; + +@Path("/oidc-client") +public class OidcClientResource { + + @Inject + @RestClient + ProtectedResourceServiceAnnotationOidcClient protectedResourceServiceAnnotationOidcClient; + + @Inject + @RestClient + ProtectedResourceServiceConfigPropertyOidcClient protectedResourceServiceConfigPropertyOidcClient; + + @GET + @Path("/annotation/user-name") + public String annotationUserName() { + return protectedResourceServiceAnnotationOidcClient.getUserName(); + } + + @GET + @Path("/config-property/user-name") + public String configPropertyUserName() { + return protectedResourceServiceConfigPropertyOidcClient.getUserName(); + } +} diff --git a/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResource.java b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResource.java new file mode 100644 index 00000000000000..af91c7906f4bb2 --- /dev/null +++ b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResource.java @@ -0,0 +1,24 @@ +package io.quarkus.oidc.client.reactive.filter; + +import java.security.Principal; + +import javax.annotation.security.RolesAllowed; +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import io.quarkus.security.Authenticated; + +@Path("/protected") +@Authenticated +public class ProtectedResource { + + @Inject + Principal principal; + + @GET + @RolesAllowed("user") + public String principalName() { + return principal.getName(); + } +} diff --git a/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceAnnotationOidcClient.java b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceAnnotationOidcClient.java new file mode 100644 index 00000000000000..d765aa3b7ebb2a --- /dev/null +++ b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceAnnotationOidcClient.java @@ -0,0 +1,17 @@ +package io.quarkus.oidc.client.reactive.filter; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.quarkus.oidc.client.filter.OidcClientFilter; + +@OidcClientFilter("annotation") +@RegisterRestClient +@Path("/") +public interface ProtectedResourceServiceAnnotationOidcClient { + + @GET + String getUserName(); +} diff --git a/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceConfigPropertyOidcClient.java b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceConfigPropertyOidcClient.java new file mode 100644 index 00000000000000..98ef7a2ce58c3c --- /dev/null +++ b/extensions/oidc-client-reactive-filter/deployment/src/test/java/io/quarkus/oidc/client/reactive/filter/ProtectedResourceServiceConfigPropertyOidcClient.java @@ -0,0 +1,17 @@ +package io.quarkus.oidc.client.reactive.filter; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.quarkus.oidc.client.filter.OidcClientFilter; + +@OidcClientFilter +@RegisterRestClient +@Path("/") +public interface ProtectedResourceServiceConfigPropertyOidcClient { + + @GET + String getUserName(); +} diff --git a/extensions/oidc-client-reactive-filter/deployment/src/test/resources/application-oidc-client-reactive-filter.properties b/extensions/oidc-client-reactive-filter/deployment/src/test/resources/application-oidc-client-reactive-filter.properties new file mode 100644 index 00000000000000..008e89aaa1e13e --- /dev/null +++ b/extensions/oidc-client-reactive-filter/deployment/src/test/resources/application-oidc-client-reactive-filter.properties @@ -0,0 +1,23 @@ +quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ +quarkus.oidc.client-id=quarkus-service-app +quarkus.oidc.credentials.secret=secret + +quarkus.oidc-client-reactive-filter.client-name=config-property +quarkus.oidc-client.config-property.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.config-property.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.config-property.credentials.client-secret.value=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.config-property.credentials.client-secret.method=POST +quarkus.oidc-client.config-property.grant.type=password +quarkus.oidc-client.config-property.grant-options.password.username=alice +quarkus.oidc-client.config-property.grant-options.password.password=alice + +quarkus.oidc-client.annotation.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.annotation.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.annotation.credentials.client-secret.value=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.annotation.credentials.client-secret.method=POST +quarkus.oidc-client.annotation.grant.type=password +quarkus.oidc-client.annotation.grant-options.password.username=jdoe +quarkus.oidc-client.annotation.grant-options.password.password=jdoe + +io.quarkus.oidc.client.reactive.filter.ProtectedResourceServiceAnnotationOidcClient/mp-rest/url=http://localhost:8080/protected +io.quarkus.oidc.client.reactive.filter.ProtectedResourceServiceConfigPropertyOidcClient/mp-rest/url=http://localhost:8080/protected \ No newline at end of file diff --git a/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/OidcClientRequestReactiveFilter.java b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/OidcClientRequestReactiveFilter.java index 41e1ed94fc1584..725fa38fd51baf 100644 --- a/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/OidcClientRequestReactiveFilter.java +++ b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/OidcClientRequestReactiveFilter.java @@ -1,64 +1,11 @@ package io.quarkus.oidc.client.reactive.filter; -import java.util.Optional; -import java.util.function.Consumer; - import javax.annotation.Priority; import javax.ws.rs.Priorities; -import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.Response; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.jboss.logging.Logger; -import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; -import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; -import io.quarkus.oidc.client.Tokens; -import io.quarkus.oidc.client.runtime.AbstractTokensProducer; -import io.quarkus.oidc.client.runtime.DisabledOidcClientException; -import io.quarkus.oidc.common.runtime.OidcConstants; +import io.quarkus.oidc.client.reactive.filter.runtime.AbstractOidcClientRequestReactiveFilter; @Priority(Priorities.AUTHENTICATION) -public class OidcClientRequestReactiveFilter extends AbstractTokensProducer implements ResteasyReactiveClientRequestFilter { - private static final Logger LOG = Logger.getLogger(OidcClientRequestReactiveFilter.class); - private static final String BEARER_SCHEME_WITH_SPACE = OidcConstants.BEARER_SCHEME + " "; - - @ConfigProperty(name = "quarkus.oidc-client-reactive-filter.client-name") - Optional clientName; - - protected void initTokens() { - if (earlyTokenAcquisition) { - LOG.debug("Token acquisition will be delayed until this filter is executed to avoid blocking an IO thread"); - } - } - - @Override - public void filter(ResteasyReactiveClientRequestContext requestContext) { - requestContext.suspend(); - - super.getTokens().subscribe().with(new Consumer<>() { - @Override - public void accept(Tokens tokens) { - requestContext.getHeaders().putSingle(HttpHeaders.AUTHORIZATION, - BEARER_SCHEME_WITH_SPACE + tokens.getAccessToken()); - requestContext.resume(); - } - }, new Consumer<>() { - @Override - public void accept(Throwable t) { - if (t instanceof DisabledOidcClientException) { - LOG.debug("Client is disabled"); - requestContext.abortWith(Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()); - } else { - LOG.debugf("Access token is not available, aborting the request with HTTP 401 error: %s", t.getMessage()); - requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build()); - } - requestContext.resume(); - } - }); - } +public class OidcClientRequestReactiveFilter extends AbstractOidcClientRequestReactiveFilter { - protected Optional clientId() { - return clientName; - } } diff --git a/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java new file mode 100644 index 00000000000000..641606308915f1 --- /dev/null +++ b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/AbstractOidcClientRequestReactiveFilter.java @@ -0,0 +1,54 @@ +package io.quarkus.oidc.client.reactive.filter.runtime; + +import java.util.function.Consumer; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.jboss.logging.Logger; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestContext; +import org.jboss.resteasy.reactive.client.spi.ResteasyReactiveClientRequestFilter; + +import io.quarkus.oidc.client.Tokens; +import io.quarkus.oidc.client.runtime.AbstractTokensProducer; +import io.quarkus.oidc.client.runtime.DisabledOidcClientException; +import io.quarkus.oidc.common.runtime.OidcConstants; + +public class AbstractOidcClientRequestReactiveFilter extends AbstractTokensProducer + implements ResteasyReactiveClientRequestFilter { + private static final Logger LOG = Logger.getLogger(AbstractOidcClientRequestReactiveFilter.class); + private static final String BEARER_SCHEME_WITH_SPACE = OidcConstants.BEARER_SCHEME + " "; + + protected void initTokens() { + if (earlyTokenAcquisition) { + LOG.debug("Token acquisition will be delayed until this filter is executed to avoid blocking an IO thread"); + } + } + + @Override + public void filter(ResteasyReactiveClientRequestContext requestContext) { + requestContext.suspend(); + + super.getTokens().subscribe().with(new Consumer<>() { + @Override + public void accept(Tokens tokens) { + requestContext.getHeaders().putSingle(HttpHeaders.AUTHORIZATION, + BEARER_SCHEME_WITH_SPACE + tokens.getAccessToken()); + requestContext.resume(); + } + }, new Consumer<>() { + @Override + public void accept(Throwable t) { + if (t instanceof DisabledOidcClientException) { + LOG.debug("Client is disabled"); + requestContext.abortWith(Response.status(Response.Status.INTERNAL_SERVER_ERROR).build()); + } else { + LOG.debugf("Access token is not available, aborting the request with HTTP 401 error: %s", t.getMessage()); + requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build()); + } + requestContext.resume(); + } + }); + } + +} diff --git a/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/OidcClientReactiveFilterConfig.java b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/OidcClientReactiveFilterConfig.java index ed57c266b8287f..f5e31c9ed6908b 100644 --- a/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/OidcClientReactiveFilterConfig.java +++ b/extensions/oidc-client-reactive-filter/runtime/src/main/java/io/quarkus/oidc/client/reactive/filter/runtime/OidcClientReactiveFilterConfig.java @@ -10,7 +10,8 @@ public class OidcClientReactiveFilterConfig { /** - * Name of the configured OidcClient. + * Name of the configured OidcClient used by the OidcClientRequestReactiveFilter. You can override this configuration + * for individual Rest clients with the `@OidcClientFilter` annotation like this: `@OidcClientFilter("jwt-secret")`. */ @ConfigItem public Optional clientName; diff --git a/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java index 839ab11187618a..40f9b529f4c4b6 100644 --- a/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java +++ b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientBuildStep.java @@ -1,5 +1,7 @@ package io.quarkus.oidc.client.deployment; +import static io.quarkus.oidc.client.deployment.OidcClientFilterDeploymentHelper.sanitize; + import java.lang.reflect.Modifier; import java.util.Objects; import java.util.Optional; @@ -201,10 +203,6 @@ private String createNamedTokensProducerFor(ClassOutput classOutput, String targ return generatedName.replace('/', '.'); } - private String sanitize(String oidcClientName) { - return oidcClientName.replaceAll("\\W+", ""); - } - public static class IsEnabled implements BooleanSupplier { OidcClientBuildTimeConfig config; diff --git a/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientFilterDeploymentHelper.java b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientFilterDeploymentHelper.java new file mode 100644 index 00000000000000..4b442c4fa17091 --- /dev/null +++ b/extensions/oidc-client/deployment/src/main/java/io/quarkus/oidc/client/deployment/OidcClientFilterDeploymentHelper.java @@ -0,0 +1,98 @@ +package io.quarkus.oidc.client.deployment; + +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Function; + +import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; +import org.jboss.jandex.DotName; + +import io.quarkus.arc.deployment.GeneratedBeanBuildItem; +import io.quarkus.arc.deployment.GeneratedBeanGizmoAdaptor; +import io.quarkus.arc.processor.DotNames; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.gizmo.ClassCreator; +import io.quarkus.gizmo.ClassOutput; +import io.quarkus.gizmo.MethodCreator; +import io.quarkus.gizmo.MethodDescriptor; +import io.quarkus.oidc.client.runtime.AbstractTokensProducer; + +/** + * Helps generate Oidc request filter based on {@link AbstractTokensProducer}. + */ +public class OidcClientFilterDeploymentHelper { + + private final Map clientNameToGeneratedClass = new HashMap<>(); + private final Class baseClass; + private final ClassOutput classOutput; + private final String targetPackage; + + public OidcClientFilterDeploymentHelper(Class baseClass, BuildProducer generatedBean) { + this.baseClass = baseClass; + this.classOutput = new GeneratedBeanGizmoAdaptor(generatedBean); + this.targetPackage = DotNames + .internalPackageNameWithTrailingSlash(DotName.createSimple(baseClass.getName())); + } + + /** + * For {@code baseClass} Xyz creates tokens producer class like follows: + * + *
+     * @Singleton
+     * @Unremovable
+     * public class Xyz_oidcClientName extends Xyz {
+     *
+     *     @Override
+     *     protected Optional clientId() {
+     *         return Optional.of("oidcClientName");
+     *     }
+     * }
+     * 
+ */ + public String getOrCreateNamedTokensProducerFor(String oidcClientName) { + Objects.requireNonNull(oidcClientName); + // do not create class for same client twice + return clientNameToGeneratedClass.computeIfAbsent(oidcClientName, new Function() { + @Override + public String apply(String s) { + final String generatedName = targetPackage + baseClass.getSimpleName() + "_" + sanitize(oidcClientName); + + try (ClassCreator creator = ClassCreator.builder().classOutput(classOutput).className(generatedName) + .superClass(baseClass).build()) { + creator.addAnnotation(DotNames.SINGLETON.toString()); + creator.addAnnotation(DotNames.UNREMOVABLE.toString()); + + try (MethodCreator clientIdMethod = creator.getMethodCreator("clientId", Optional.class)) { + clientIdMethod.setModifiers(Modifier.PROTECTED); + + clientIdMethod.returnValue(clientIdMethod.invokeStaticMethod( + MethodDescriptor.ofMethod(Optional.class, "of", Optional.class, Object.class), + clientIdMethod.load(oidcClientName))); + } + } + + return generatedName.replace('/', '.'); + } + }); + } + + public DotName getOrCreateFilter(String oidcClientName) { + return DotName.createSimple(getOrCreateNamedTokensProducerFor(oidcClientName)); + } + + public static String getClientName(AnnotationInstance annotationInstance) { + final AnnotationValue annotationValue = annotationInstance.value(); + if (annotationValue != null && !annotationValue.asString().isEmpty()) { + return annotationValue.asString(); + } + return null; + } + + public static String sanitize(String oidcClientName) { + return oidcClientName.replaceAll("\\W+", ""); + } +} diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientFilter.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientFilter.java index 65574b10e6f008..6a8f632c16859d 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientFilter.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientFilter.java @@ -10,4 +10,10 @@ @Retention(RetentionPolicy.RUNTIME) @Documented public @interface OidcClientFilter { + + /** + * @return name of the OIDC client that should be used to acquire the tokens. + */ + String value() default ""; + } diff --git a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/AbstractTokensProducer.java b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/AbstractTokensProducer.java index 784508e17bb542..f8c942e5d30708 100644 --- a/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/AbstractTokensProducer.java +++ b/extensions/oidc-client/runtime/src/main/java/io/quarkus/oidc/client/runtime/AbstractTokensProducer.java @@ -6,8 +6,6 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; -import org.eclipse.microprofile.config.inject.ConfigProperty; - import io.quarkus.arc.Arc; import io.quarkus.oidc.client.OidcClient; import io.quarkus.oidc.client.OidcClients; @@ -17,18 +15,26 @@ public abstract class AbstractTokensProducer { private OidcClient oidcClient; + protected boolean earlyTokenAcquisition = true; + @Inject - @ConfigProperty(name = "quarkus.oidc-client.early-tokens-acquisition") - public boolean earlyTokenAcquisition; + public OidcClientsConfig oidcClientsConfig; final TokensHelper tokensHelper = new TokensHelper(); @PostConstruct public void init() { - OidcClients oidcClients = Arc.container().instance(OidcClients.class).get(); Optional clientId = Objects.requireNonNull(clientId(), "clientId must not be null"); - oidcClient = clientId.isPresent() ? Objects.requireNonNull(oidcClients.getClient(clientId.get()), "Unknown client") - : oidcClients.getClient(); + OidcClients oidcClients = Arc.container().instance(OidcClients.class).get(); + if (clientId.isPresent()) { + // static named OidcClient + oidcClient = Objects.requireNonNull(oidcClients.getClient(clientId.get()), "Unknown client"); + earlyTokenAcquisition = oidcClientsConfig.namedClients.get(clientId.get()).earlyTokensAcquisition; + } else { + // default OidcClient + earlyTokenAcquisition = oidcClientsConfig.defaultClient.earlyTokensAcquisition; + oidcClient = oidcClients.getClient(); + } initTokens(); } diff --git a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientPredicateProviderBuildItem.java b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientPredicateProviderBuildItem.java new file mode 100644 index 00000000000000..93547db897f3e2 --- /dev/null +++ b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientPredicateProviderBuildItem.java @@ -0,0 +1,36 @@ +package io.quarkus.restclient.deployment; + +import java.util.function.Predicate; + +import org.jboss.jandex.ClassInfo; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Register provider against every Rest client matching predicate. + */ +public final class RestClientPredicateProviderBuildItem extends MultiBuildItem { + + private final String providerClass; + private final Predicate matcher; + + /** + * Register JAX-RS client provider against Rest clients matching {@code matcher} condition. + */ + public RestClientPredicateProviderBuildItem(String providerClass, Predicate matcher) { + this.providerClass = providerClass; + this.matcher = matcher; + } + + public String getProviderClass() { + return providerClass; + } + + /** + * Test whether the {@link #providerClass} should be added to {@code restClientClassInfo} as provider. + */ + boolean appliesTo(ClassInfo restClientClassInfo) { + return matcher.test(restClientClassInfo); + } + +} diff --git a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java index 537ba5c392865f..42fc7d921fc624 100644 --- a/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java +++ b/extensions/resteasy-classic/rest-client/deployment/src/main/java/io/quarkus/restclient/deployment/RestClientProcessor.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Predicate; import java.util.stream.Collectors; import javax.enterprise.context.SessionScoped; @@ -174,6 +175,23 @@ UnremovableBeanBuildItem makeConfigUnremovable() { return UnremovableBeanBuildItem.beanTypes(RestClientsConfig.class); } + @BuildStep + List transformAnnotationProvider( + List annotationProviders) { + List result = new ArrayList<>(); + for (RestClientAnnotationProviderBuildItem annotationProvider : annotationProviders) { + result.add(new RestClientPredicateProviderBuildItem(annotationProvider.getProviderClass().getName(), + new Predicate() { + @Override + public boolean test(ClassInfo classInfo) { + // register the provider to every Rest client annotated with annotationName + return classInfo.hasAnnotation(annotationProvider.getAnnotationName()); + } + })); + } + return result; + } + @BuildStep void processInterfaces( CombinedIndexBuildItem combinedIndexBuildItem, @@ -181,7 +199,7 @@ void processInterfaces( Capabilities capabilities, Optional metricsCapability, PackageConfig packageConfig, - List restClientAnnotationProviders, + List restClientProviders, BuildProducer proxyDefinition, BuildProducer reflectiveClass, BuildProducer reflectiveHierarchy, @@ -244,28 +262,28 @@ void processInterfaces( configurator.addQualifier(REST_CLIENT); final Optional configKey = getConfigKey(entry.getValue()); final ScopeInfo scope = computeDefaultScope(capabilities, config, entry, configKey); - final List> annotationProviders = checkAnnotationProviders(entry.getValue(), - restClientAnnotationProviders); + final List clientProviders = checkRestClientProviders(entry.getValue(), + restClientProviders); configurator.scope(scope); configurator.creator(m -> { // return new RestClientBase(proxyType, baseUri).create(); ResultHandle interfaceHandle = m.loadClassFromTCCL(restClientName.toString()); ResultHandle baseUriHandle = m.load(getAnnotationParameter(entry.getValue(), "baseUri")); ResultHandle configKeyHandle = configKey.isPresent() ? m.load(configKey.get()) : m.loadNull(); - ResultHandle annotationProvidersHandle; - if (!annotationProviders.isEmpty()) { - annotationProvidersHandle = m.newArray(Class.class, annotationProviders.size()); - for (int i = 0; i < annotationProviders.size(); i++) { - m.writeArrayValue(annotationProvidersHandle, i, m.loadClassFromTCCL(annotationProviders.get(i))); + ResultHandle restClientProvidersHandle; + if (!clientProviders.isEmpty()) { + restClientProvidersHandle = m.newArray(Class.class, clientProviders.size()); + for (int i = 0; i < clientProviders.size(); i++) { + m.writeArrayValue(restClientProvidersHandle, i, m.loadClassFromTCCL(clientProviders.get(i))); } } else { - annotationProvidersHandle = m.loadNull(); + restClientProvidersHandle = m.loadNull(); } ResultHandle baseHandle = m.newInstance( MethodDescriptor.ofConstructor(RestClientBase.class, Class.class, String.class, String.class, Class[].class), - interfaceHandle, baseUriHandle, configKeyHandle, annotationProvidersHandle); + interfaceHandle, baseUriHandle, configKeyHandle, restClientProvidersHandle); ResultHandle ret = m.invokeVirtualMethod( MethodDescriptor.ofMethod(RestClientBase.class, "create", Object.class), baseHandle); m.returnValue(ret); @@ -292,9 +310,9 @@ private boolean isRequired(Capabilities capabilities, && metricsCapability.get().metricsSupported(MetricsFactory.MICROMETER))); } - private static List> checkAnnotationProviders(ClassInfo classInfo, - List restClientAnnotationProviders) { - return restClientAnnotationProviders.stream().filter(p -> (classInfo.classAnnotation(p.getAnnotationName()) != null)) + private static List checkRestClientProviders(ClassInfo classInfo, + List restClientProviders) { + return restClientProviders.stream().filter(p -> p.appliesTo(classInfo)) .map(p -> p.getProviderClass()).collect(Collectors.toList()); } diff --git a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java index 9b8136cc6e9fbe..df5ccb883169e5 100644 --- a/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java +++ b/extensions/resteasy-classic/rest-client/runtime/src/main/java/io/quarkus/restclient/runtime/RestClientBase.java @@ -36,22 +36,22 @@ public class RestClientBase { private final Class proxyType; private final String baseUriFromAnnotation; - private final Class[] annotationProviders; + private final Class[] clientProviders; private final RestClientsConfig configRoot; private final String configKey; public RestClientBase(Class proxyType, String baseUriFromAnnotation, String configKey, - Class[] annotationProviders) { - this(proxyType, baseUriFromAnnotation, configKey, annotationProviders, + Class[] clientProviders) { + this(proxyType, baseUriFromAnnotation, configKey, clientProviders, RestClientsConfig.getInstance()); } RestClientBase(Class proxyType, String baseUriFromAnnotation, String configKey, - Class[] annotationProviders, RestClientsConfig configRoot) { + Class[] clientProviders, RestClientsConfig configRoot) { this.proxyType = proxyType; this.baseUriFromAnnotation = baseUriFromAnnotation; this.configKey = configKey; - this.annotationProviders = annotationProviders; + this.clientProviders = clientProviders; this.configRoot = configRoot; } @@ -264,8 +264,8 @@ protected void configureProviders(RestClientBuilder builder) { if (providers.isPresent()) { registerProviders(builder, providers.get()); } - if (annotationProviders != null) { - for (Class annotationProvider : annotationProviders) { + if (clientProviders != null) { + for (Class annotationProvider : clientProviders) { builder.register(annotationProvider); } } diff --git a/integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java b/integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java index 2475b7cded8ec2..835ad9c17691da 100644 --- a/integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java +++ b/integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/FrontendResource.java @@ -19,6 +19,10 @@ public class FrontendResource { @RestClient ProtectedResourceServiceReactiveFilter protectedResourceServiceReactiveFilter; + @Inject + @RestClient + ProtectedResourceServiceNamedFilter protectedResourceServiceNamedFilter; + @GET @Path("userNameCustomFilter") @Produces("text/plain") @@ -32,4 +36,11 @@ public Uni userName() { public Uni userNameReactive() { return protectedResourceServiceReactiveFilter.getUserName(); } + + @GET + @Path("userNameNamedFilter") + @Produces("text/plain") + public Uni userNameNamedFilter() { + return protectedResourceServiceNamedFilter.getUserName(); + } } diff --git a/integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNamedFilter.java b/integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNamedFilter.java new file mode 100644 index 00000000000000..04d703f5d5f391 --- /dev/null +++ b/integration-tests/oidc-client-reactive/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNamedFilter.java @@ -0,0 +1,21 @@ +package io.quarkus.it.keycloak; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.quarkus.oidc.client.filter.OidcClientFilter; +import io.smallrye.mutiny.Uni; + +@RegisterRestClient +@OidcClientFilter("named-client") +@Path("/") +public interface ProtectedResourceServiceNamedFilter { + + @GET + @Produces("text/plain") + @Path("userNameReactive") + Uni getUserName(); +} diff --git a/integration-tests/oidc-client-reactive/src/main/resources/application.properties b/integration-tests/oidc-client-reactive/src/main/resources/application.properties index 9a214ffeb8d972..26e2d60ffb0e40 100644 --- a/integration-tests/oidc-client-reactive/src/main/resources/application.properties +++ b/integration-tests/oidc-client-reactive/src/main/resources/application.properties @@ -10,8 +10,16 @@ quarkus.oidc-client.grant.type=password quarkus.oidc-client.grant-options.password.username=alice quarkus.oidc-client.grant-options.password.password=alice +quarkus.oidc-client.named-client.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.named-client.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.named-client.credentials.secret=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.named-client.grant.type=password +quarkus.oidc-client.named-client.grant-options.password.username=jdoe +quarkus.oidc-client.named-client.grant-options.password.password=jdoe + io.quarkus.it.keycloak.ProtectedResourceServiceCustomFilter/mp-rest/url=http://localhost:8081/protected io.quarkus.it.keycloak.ProtectedResourceServiceReactiveFilter/mp-rest/url=http://localhost:8081/protected +io.quarkus.it.keycloak.ProtectedResourceServiceNamedFilter/mp-rest/url=http://localhost:8081/protected quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".min-level=TRACE quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".level=TRACE diff --git a/integration-tests/oidc-client-reactive/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java b/integration-tests/oidc-client-reactive/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java index f216770d3c413b..ab999b6a55d01d 100644 --- a/integration-tests/oidc-client-reactive/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java +++ b/integration-tests/oidc-client-reactive/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java @@ -35,6 +35,15 @@ public void testGetUserNameCustomFilter() { .body(equalTo("alice")); } + @Test + public void testGetUserNameNamedFilter() { + RestAssured.given().header("Accept", "text/plain") + .when().get("/frontend/userNameNamedFilter") + .then() + .statusCode(200) + .body(equalTo("jdoe")); + } + @Test public void testGetUserNameReactive() { RestAssured.given().header("Accept", "text/plain") diff --git a/integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/FrontendResource.java b/integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/FrontendResource.java index dd163cea232af5..de5fe9f39ee159 100644 --- a/integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/FrontendResource.java +++ b/integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/FrontendResource.java @@ -25,6 +25,10 @@ public class FrontendResource { @RestClient ProtectedResourceServiceNoOidcClient protectedResourceServiceNoOidcClient; + @Inject + @RestClient + ProtectedResourceServiceNonDefaultOidcClient protectedResourceServiceNonDefaultOidcClient; + @Inject ManagedExecutor managedExecutor; @@ -37,6 +41,12 @@ public String userNameOidcClient() { return protectedResourceServiceOidcClient.getUserName(); } + @GET + @Path("userNonDefaultOidcClient") + public String userNameNonDefaultOidcClient() { + return protectedResourceServiceNonDefaultOidcClient.getUserName(); + } + @GET @Path("userOidcClientManagedExecutor") public String userNameOidcClientManagedExecutor() throws Exception { diff --git a/integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNonDefaultOidcClient.java b/integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNonDefaultOidcClient.java new file mode 100644 index 00000000000000..676632398d3a15 --- /dev/null +++ b/integration-tests/oidc-client/src/main/java/io/quarkus/it/keycloak/ProtectedResourceServiceNonDefaultOidcClient.java @@ -0,0 +1,18 @@ +package io.quarkus.it.keycloak; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; + +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +import io.quarkus.oidc.client.filter.OidcClientFilter; + +@RegisterRestClient +@OidcClientFilter("non-default-client") +@Path("/") +public interface ProtectedResourceServiceNonDefaultOidcClient { + + @GET + String getUserName(); + +} diff --git a/integration-tests/oidc-client/src/main/resources/application.properties b/integration-tests/oidc-client/src/main/resources/application.properties index 6351861a8f3abb..d020d5631dc32f 100644 --- a/integration-tests/oidc-client/src/main/resources/application.properties +++ b/integration-tests/oidc-client/src/main/resources/application.properties @@ -16,10 +16,18 @@ quarkus.oidc-client.named.grant.type=password quarkus.oidc-client.named.grant-options.password.username=alice quarkus.oidc-client.named.grant-options.password.password=alice +quarkus.oidc-client.non-default-client.auth-server-url=${quarkus.oidc.auth-server-url} +quarkus.oidc-client.non-default-client.client-id=${quarkus.oidc.client-id} +quarkus.oidc-client.non-default-client.credentials.secret=${quarkus.oidc.credentials.secret} +quarkus.oidc-client.non-default-client.grant.type=password +quarkus.oidc-client.non-default-client.grant-options.password.username=bob +quarkus.oidc-client.non-default-client.grant-options.password.password=bob + io.quarkus.it.keycloak.ProtectedResourceServiceRegisterProvider/mp-rest/url=http://localhost:8081/protected io.quarkus.it.keycloak.ProtectedResourceServiceOidcClient/mp-rest/url=http://localhost:8081/protected io.quarkus.it.keycloak.ProtectedResourceServiceNamedOidcClient/mp-rest/url=http://localhost:8081/protected io.quarkus.it.keycloak.ProtectedResourceServiceNoOidcClient/mp-rest/url=http://localhost:8081/protected +io.quarkus.it.keycloak.ProtectedResourceServiceNonDefaultOidcClient/mp-rest/url=http://localhost:8081/protected quarkus.tls.trust-all=true quarkus.log.category."io.quarkus.oidc.client.runtime.OidcClientImpl".min-level=TRACE diff --git a/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java b/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java index a760650e66bbd4..a5f70b2f08f758 100644 --- a/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java +++ b/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/KeycloakRealmResourceManager.java @@ -38,6 +38,7 @@ public Map start() { realm.getClients().add(createClient("quarkus-app")); realm.getUsers().add(createUser("alice", "user")); + realm.getUsers().add(createUser("bob", "user")); try { RestAssured diff --git a/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java b/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java index c64965dc44ce02..94c1747f1fc1f1 100644 --- a/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java +++ b/integration-tests/oidc-client/src/test/java/io/quarkus/it/keycloak/OidcClientTest.java @@ -36,6 +36,14 @@ public void testGetUserNameOidcClient() { .body(equalTo("alice")); } + @Test + public void testGetUserNameNonDefaultOidcClient() { + RestAssured.when().get("/frontend/userNonDefaultOidcClient") + .then() + .statusCode(200) + .body(equalTo("bob")); + } + @Test public void testGetUserNameOidcClientManagedExecutor() { RestAssured.when().get("/frontend/userOidcClientManagedExecutor")