Skip to content

Commit ea60ff6

Browse files
authored
Merge pull request #50548 from michalvavrik/feature/fix-extended-oidc-client-request-filter
OIDC Clients: allow to mix `@OidcClientFilter` annotation with extended builtin filters intended for `@RegisterProvider` annotation
2 parents 57ae21d + 4395006 commit ea60ff6

File tree

17 files changed

+409
-86
lines changed

17 files changed

+409
-86
lines changed

extensions/oidc-client-filter/deployment/src/main/java/io/quarkus/oidc/client/filter/deployment/OidcClientFilterBuildStep.java

Lines changed: 35 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.oidc.client.filter.deployment;
22

3+
import static io.quarkus.oidc.client.deployment.OidcClientFilterDeploymentHelper.DEFAULT_OIDC_REQUEST_FILTER_NAME;
34
import static io.quarkus.oidc.client.deployment.OidcClientFilterDeploymentHelper.detectCustomFiltersThatRequireResponseFilter;
45

56
import java.util.Collection;
@@ -26,7 +27,6 @@
2627
import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter;
2728
import io.quarkus.oidc.client.filter.runtime.DetectUnauthorizedClientResponseFilter;
2829
import io.quarkus.oidc.client.filter.runtime.OidcClientFilterConfig;
29-
import io.quarkus.restclient.deployment.RestClientAnnotationProviderBuildItem;
3030
import io.quarkus.restclient.deployment.RestClientPredicateProviderBuildItem;
3131
import io.quarkus.resteasy.common.spi.ResteasyJaxrsProviderBuildItem;
3232

@@ -42,17 +42,16 @@ void registerProvider(BuildProducer<AdditionalBeanBuildItem> additionalBeans,
4242
BuildProducer<ReflectiveClassBuildItem> reflectiveClass,
4343
NamedOidcClientFilterBuildItem namedOidcClientFilterBuildItem,
4444
BuildProducer<ResteasyJaxrsProviderBuildItem> jaxrsProviders,
45-
BuildProducer<RestClientPredicateProviderBuildItem> restPredicateProvider,
46-
BuildProducer<RestClientAnnotationProviderBuildItem> restAnnotationProvider) {
45+
BuildProducer<RestClientPredicateProviderBuildItem> restPredicateProvider) {
4746

4847
additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(OidcClientRequestFilter.class));
4948
reflectiveClass.produce(ReflectiveClassBuildItem.builder(OidcClientRequestFilter.class)
5049
.reason(getClass().getName())
5150
.methods().fields().build());
52-
final Set<String> namedFilterClientClasses = namedOidcClientFilterBuildItem.namedFilterClientClasses;
5351

5452
// register default request filter provider against the rest of the clients (client != namedFilterClientClasses)
5553
if (config.registerFilter()) {
54+
final Set<String> namedFilterClientClasses = namedOidcClientFilterBuildItem.namedFilterClientClasses;
5655
if (namedFilterClientClasses.isEmpty()) {
5756
// register default request filter as global rest client provider
5857
jaxrsProviders.produce(new ResteasyJaxrsProviderBuildItem(OidcClientRequestFilter.class.getName()));
@@ -69,7 +68,7 @@ public boolean test(ClassInfo restClientClassInfo) {
6968
return !namedFilterClientClasses.contains(restClientClassInfo.name().toString());
7069
}
7170
};
72-
// register all clients without @OidcClientFilter("clientName")
71+
// register all clients without @OidcClientFilter
7372
restPredicateProvider
7473
.produce(new RestClientPredicateProviderBuildItem(OidcClientRequestFilter.class.getName(),
7574
isNotNamedFilterClient));
@@ -78,33 +77,6 @@ public boolean test(ClassInfo restClientClassInfo) {
7877
DetectUnauthorizedClientResponseFilter.class.getName(), isNotNamedFilterClient));
7978
}
8079
}
81-
} else {
82-
if (namedFilterClientClasses.isEmpty()) {
83-
// register default request filter against all the Rest clients annotated with @OidcClientFilter
84-
restAnnotationProvider.produce(new RestClientAnnotationProviderBuildItem(OIDC_CLIENT_FILTER,
85-
OidcClientRequestFilter.class));
86-
if (config.refreshOnUnauthorized()) {
87-
restAnnotationProvider.produce(new RestClientAnnotationProviderBuildItem(OIDC_CLIENT_FILTER,
88-
DetectUnauthorizedClientResponseFilter.class));
89-
}
90-
} else {
91-
// register default request filter against Rest client annotated with @OidcClientFilter without named ones
92-
var isNotNamedFilterClient = new Predicate<ClassInfo>() {
93-
// test whether the provider should be added restClientClassInfo
94-
@Override
95-
public boolean test(ClassInfo restClientClassInfo) {
96-
// do not register default request filter as provider against Rest client with named filter
97-
return restClientClassInfo.hasAnnotation(OIDC_CLIENT_FILTER)
98-
&& !namedFilterClientClasses.contains(restClientClassInfo.name().toString());
99-
}
100-
};
101-
restPredicateProvider.produce(new RestClientPredicateProviderBuildItem(OidcClientRequestFilter.class.getName(),
102-
isNotNamedFilterClient));
103-
if (config.refreshOnUnauthorized()) {
104-
restPredicateProvider.produce(new RestClientPredicateProviderBuildItem(
105-
DetectUnauthorizedClientResponseFilter.class.getName(), isNotNamedFilterClient));
106-
}
107-
}
10880
}
10981
}
11082

@@ -114,46 +86,44 @@ NamedOidcClientFilterBuildItem registerNamedProviders(BuildProducer<ReflectiveCl
11486
BuildProducer<RestClientPredicateProviderBuildItem> restPredicateProvider,
11587
BuildProducer<GeneratedBeanBuildItem> generatedBean) {
11688

117-
// create and register named request filter for each @OidcClientFilter("clientName")
89+
// create and register request filter for each @OidcClientFilter annotation instance
11890
final var helper = new OidcClientFilterDeploymentHelper<>(AbstractOidcClientRequestFilter.class, generatedBean,
11991
config.refreshOnUnauthorized());
12092
Collection<AnnotationInstance> instances = indexBuildItem.getIndex().getAnnotations(OIDC_CLIENT_FILTER);
12193
final Set<String> namedFilterClientClasses = new HashSet<>();
12294
for (AnnotationInstance instance : instances) {
123-
// get client name from annotation @OidcClientFilter("clientName")
124-
final String clientName = OidcClientFilterDeploymentHelper.getClientName(instance);
125-
// do not create & register named filter for the OidcClient registered through configuration property
126-
// as default request filter got it covered
127-
if (clientName != null && !clientName.equals(config.clientName().orElse(null))) {
128-
129-
// create named filter class for named OidcClient
130-
// we generate exactly one custom filter for each named client specified through annotation
131-
final var generatedProvider = helper.getOrCreateNamedTokensProducerFor(clientName);
132-
final var targetRestClient = instance.target().asClass().name().toString();
133-
namedFilterClientClasses.add(targetRestClient);
134-
135-
// register for reflection
136-
reflectiveClass.produce(ReflectiveClassBuildItem.builder(generatedProvider).methods()
137-
.reason(getClass().getName())
138-
.fields().serialization(true).build());
139-
140-
var isAnnotatedRestClient = new Predicate<ClassInfo>() {
141-
// test whether the provider should be added restClientClassInfo
142-
@Override
143-
public boolean test(ClassInfo restClientClassInfo) {
144-
return targetRestClient.equals(restClientClassInfo.name().toString());
145-
}
146-
};
147-
148-
// register named request filter provider against Rest client
149-
restPredicateProvider.produce(new RestClientPredicateProviderBuildItem(generatedProvider,
150-
isAnnotatedRestClient));
95+
// get client name from annotation @OidcClientFilter
96+
String clientName = OidcClientFilterDeploymentHelper.getClientName(instance);
97+
if (clientName == null) {
98+
clientName = config.clientName().orElse(DEFAULT_OIDC_REQUEST_FILTER_NAME);
99+
}
151100

152-
if (config.refreshOnUnauthorized()) {
153-
restPredicateProvider
154-
.produce(new RestClientPredicateProviderBuildItem(
155-
DetectUnauthorizedClientResponseFilter.class.getName(), isAnnotatedRestClient));
101+
// we generate exactly one custom filter for each @OidcClientFilter client specified through annotation
102+
final var generatedProvider = helper.getOrCreateNamedTokensProducerFor(clientName);
103+
final var targetRestClient = instance.target().asClass().name().toString();
104+
namedFilterClientClasses.add(targetRestClient);
105+
106+
// register for reflection
107+
reflectiveClass.produce(ReflectiveClassBuildItem.builder(generatedProvider).methods()
108+
.reason(getClass().getName())
109+
.fields().serialization(true).build());
110+
111+
var isAnnotatedRestClient = new Predicate<ClassInfo>() {
112+
// test whether the provider should be added restClientClassInfo
113+
@Override
114+
public boolean test(ClassInfo restClientClassInfo) {
115+
return targetRestClient.equals(restClientClassInfo.name().toString());
156116
}
117+
};
118+
119+
// register named request filter provider against Rest client
120+
restPredicateProvider.produce(new RestClientPredicateProviderBuildItem(generatedProvider,
121+
isAnnotatedRestClient));
122+
123+
if (config.refreshOnUnauthorized()) {
124+
restPredicateProvider
125+
.produce(new RestClientPredicateProviderBuildItem(
126+
DetectUnauthorizedClientResponseFilter.class.getName(), isAnnotatedRestClient));
157127
}
158128
}
159129
return new NamedOidcClientFilterBuildItem(namedFilterClientClasses);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package io.quarkus.oidc.client.filter;
2+
3+
import jakarta.annotation.Priority;
4+
import jakarta.ws.rs.Priorities;
5+
6+
@Priority(Priorities.AUTHENTICATION)
7+
public class ExtendedOidcClientRequestFilter extends OidcClientRequestFilter {
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.quarkus.oidc.client.filter;
2+
3+
import static org.hamcrest.Matchers.equalTo;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
8+
import io.quarkus.test.QuarkusDevModeTest;
9+
import io.quarkus.test.common.QuarkusTestResource;
10+
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
11+
import io.restassured.RestAssured;
12+
13+
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
14+
public class ExtendedOidcClientRequestFilterDevModeTest {
15+
16+
private static final Class<?>[] testClasses = {
17+
ProtectedResource.class,
18+
ProtectedResourceServiceNamedOidcClient.class,
19+
ProtectedResourceServiceConfigPropertyOidcClient.class,
20+
ProtectedResourceServiceExtendedOidcClientRequestFilter.class,
21+
NamedOidcClientResource.class,
22+
ExtendedOidcClientRequestFilter.class,
23+
ExtendedOidcClientRequestFilterResource.class
24+
};
25+
26+
@RegisterExtension
27+
static final QuarkusDevModeTest test = new QuarkusDevModeTest()
28+
.withApplicationRoot((jar) -> jar
29+
.addClasses(testClasses)
30+
.addAsResource("application-extended-oidc-client-request-filter.properties", "application.properties"));
31+
32+
@Test
33+
public void testGerUserConfigPropertyAndAnnotation() {
34+
// OidcClient selected via @OidcClient("clientName")
35+
RestAssured.when().get("/named-oidc-client/user-name")
36+
.then()
37+
.statusCode(200)
38+
.body(equalTo("jdoe"));
39+
40+
// @OidcClientFilter: OidcClient selected via `quarkus.oidc-client-filter.client-name=config-property`
41+
RestAssured.when().get("/config-property-oidc-client/annotation/user-name")
42+
.then()
43+
.statusCode(200)
44+
.body(equalTo("alice"));
45+
46+
// @RegisterProvider(ExtendedOidcClientRequestFilter.class)
47+
// OidcClient selected via `quarkus.oidc-client-filter.client-name=config-property`
48+
// ExtendedOidcClientRequestFilter extends OidcClientRequestFilter
49+
RestAssured.when().get("/config-property-oidc-client/extended-provider/user-name")
50+
.then()
51+
.statusCode(200)
52+
.body(equalTo("alice"));
53+
}
54+
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.quarkus.oidc.client.filter;
2+
3+
import jakarta.inject.Inject;
4+
import jakarta.ws.rs.GET;
5+
import jakarta.ws.rs.Path;
6+
7+
import org.eclipse.microprofile.rest.client.inject.RestClient;
8+
9+
@Path("/config-property-oidc-client")
10+
public class ExtendedOidcClientRequestFilterResource {
11+
12+
@Inject
13+
@RestClient
14+
ProtectedResourceServiceConfigPropertyOidcClient protectedResourceServiceConfigPropertyOidcClient;
15+
16+
@Inject
17+
@RestClient
18+
ProtectedResourceServiceExtendedOidcClientRequestFilter protectedResourceServiceExtendedOidcClientRequestFilter;
19+
20+
@GET
21+
@Path("/annotation/user-name")
22+
public String userName() {
23+
return protectedResourceServiceConfigPropertyOidcClient.getUserName();
24+
}
25+
26+
@GET
27+
@Path("/extended-provider/user-name")
28+
public String extendedOidcClientRequestFilterUserName() {
29+
return protectedResourceServiceExtendedOidcClientRequestFilter.getUserName();
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.quarkus.oidc.client.filter;
2+
3+
import jakarta.ws.rs.GET;
4+
import jakarta.ws.rs.Path;
5+
6+
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
7+
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
8+
9+
@RegisterProvider(ExtendedOidcClientRequestFilter.class)
10+
@RegisterRestClient
11+
@Path("/")
12+
public interface ProtectedResourceServiceExtendedOidcClientRequestFilter {
13+
14+
@GET
15+
String getUserName();
16+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
2+
quarkus.oidc.client-id=quarkus-service-app
3+
quarkus.oidc.credentials.secret=secret
4+
5+
quarkus.resteasy-client-oidc-filter.client-name=config-property
6+
quarkus.oidc-client.config-property.auth-server-url=${quarkus.oidc.auth-server-url}
7+
quarkus.oidc-client.config-property.client-id=${quarkus.oidc.client-id}
8+
quarkus.oidc-client.config-property.credentials.client-secret.value=${quarkus.oidc.credentials.secret}
9+
quarkus.oidc-client.config-property.credentials.client-secret.method=POST
10+
quarkus.oidc-client.config-property.grant.type=password
11+
quarkus.oidc-client.config-property.grant-options.password.username=alice
12+
quarkus.oidc-client.config-property.grant-options.password.password=alice
13+
14+
quarkus.oidc-client.named.auth-server-url=${quarkus.oidc.auth-server-url}
15+
quarkus.oidc-client.named.client-id=${quarkus.oidc.client-id}
16+
quarkus.oidc-client.named.credentials.client-secret.value=${quarkus.oidc.credentials.secret}
17+
quarkus.oidc-client.named.credentials.client-secret.method=POST
18+
quarkus.oidc-client.named.grant.type=password
19+
quarkus.oidc-client.named.grant-options.password.username=jdoe
20+
quarkus.oidc-client.named.grant-options.password.password=jdoe
21+
22+
io.quarkus.oidc.client.filter.ProtectedResourceServiceNamedOidcClient/mp-rest/url=http://localhost:8080/protected
23+
io.quarkus.oidc.client.filter.ProtectedResourceServiceConfigPropertyOidcClient/mp-rest/url=http://localhost:8080/protected
24+
io.quarkus.oidc.client.filter.ProtectedResourceServiceExtendedOidcClientRequestFilter/mp-rest/url=http://localhost:8080/protected

extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/OidcClientRequestFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
public class OidcClientRequestFilter extends AbstractOidcClientRequestFilter {
1818

1919
@Inject
20-
OidcClientFilterConfig oidcClientFilterConfig;
20+
public OidcClientFilterConfig oidcClientFilterConfig;
2121

2222
protected Optional<String> clientId() {
2323
return oidcClientFilterConfig.clientName();

extensions/oidc-client-filter/runtime/src/main/java/io/quarkus/oidc/client/filter/runtime/AbstractOidcClientRequestFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import io.quarkus.oidc.client.runtime.DisabledOidcClientException;
1313
import io.quarkus.oidc.common.runtime.OidcConstants;
1414

15-
public class AbstractOidcClientRequestFilter extends AbstractTokensProducer implements ClientRequestFilter {
15+
public abstract class AbstractOidcClientRequestFilter extends AbstractTokensProducer implements ClientRequestFilter {
1616
private static final Logger LOG = Logger.getLogger(AbstractOidcClientRequestFilter.class);
1717
private static final String BEARER_SCHEME_WITH_SPACE = OidcConstants.BEARER_SCHEME + " ";
1818
static final String REQUEST_FILTER_KEY = "io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter#this";

extensions/oidc-client-reactive-filter/deployment/src/main/java/io/quarkus/oidc/client/reactive/filter/deployment/OidcClientReactiveFilterBuildStep.java

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.quarkus.oidc.client.reactive.filter.deployment;
22

3+
import static io.quarkus.oidc.client.deployment.OidcClientFilterDeploymentHelper.DEFAULT_OIDC_REQUEST_FILTER_NAME;
34
import static io.quarkus.oidc.client.deployment.OidcClientFilterDeploymentHelper.detectCustomFiltersThatRequireResponseFilter;
45

56
import java.util.Collection;
@@ -35,13 +36,10 @@
3536
public class OidcClientReactiveFilterBuildStep {
3637

3738
private static final DotName OIDC_CLIENT_FILTER = DotName.createSimple(OidcClientFilter.class.getName());
38-
private static final DotName OIDC_CLIENT_REQUEST_REACTIVE_FILTER = DotName
39-
.createSimple(OidcClientRequestReactiveFilter.class.getName());
4039
OidcClientReactiveFilterConfig oidcClientReactiveFilterConfig;
4140
private static final DotName DETECT_401_RESPONSE_FILTER = DotName
4241
.createSimple(DetectUnauthorizedClientResponseFilter.class.getName());
4342

44-
// we simply pretend that @OidcClientFilter means @RegisterProvider(OidcClientRequestReactiveFilter.class)
4543
@BuildStep
4644
void oidcClientFilterSupport(CombinedIndexBuildItem indexBuildItem, BuildProducer<GeneratedBeanBuildItem> generatedBean,
4745
BuildProducer<RegisterProviderAnnotationInstanceBuildItem> producer) {
@@ -52,17 +50,14 @@ void oidcClientFilterSupport(CombinedIndexBuildItem indexBuildItem, BuildProduce
5250
for (AnnotationInstance instance : instances) {
5351

5452
// get client name from annotation @OidcClientFilter("clientName")
55-
final String clientName = OidcClientFilterDeploymentHelper.getClientName(instance);
56-
final AnnotationValue valueAttr;
57-
if (clientName != null && !clientName.equals(oidcClientReactiveFilterConfig.clientName().orElse(null))) {
58-
// create and use custom filter for named OidcClient
59-
// we generate exactly one custom filter for each named client specified through annotation
60-
valueAttr = createClassValue(helper.getOrCreateFilter(clientName));
61-
} else {
62-
// use default filter for either default OidcClient or the client configured with config properties
63-
valueAttr = createClassValue(OIDC_CLIENT_REQUEST_REACTIVE_FILTER);
53+
String clientName = OidcClientFilterDeploymentHelper.getClientName(instance);
54+
if (clientName == null) {
55+
clientName = oidcClientReactiveFilterConfig.clientName().orElse(DEFAULT_OIDC_REQUEST_FILTER_NAME);
6456
}
6557

58+
// we generate exactly one custom filter for each named client specified through annotation
59+
final AnnotationValue valueAttr = createClassValue(helper.getOrCreateFilter(clientName));
60+
6661
final AnnotationValue priorityAttr = AnnotationValue.createIntegerValue("priority", Priorities.AUTHENTICATION);
6762
String targetClass = instance.target().asClass().name().toString();
6863
producer.produce(new RegisterProviderAnnotationInstanceBuildItem(targetClass, AnnotationInstance.create(

0 commit comments

Comments
 (0)