Skip to content

Commit d8dc654

Browse files
committed
feat(oidc-client): support @OidcClientFilter on methods
1 parent 50d1785 commit d8dc654

File tree

14 files changed

+602
-110
lines changed

14 files changed

+602
-110
lines changed

docs/src/main/asciidoc/security-openid-connect-client-reference.adoc

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,30 @@ public interface ProtectedResourceService {
551551

552552
or
553553

554+
[source,java]
555+
----
556+
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
557+
import io.quarkus.oidc.client.filter.OidcClientFilter;
558+
import io.smallrye.mutiny.Uni;
559+
import jakarta.ws.rs.GET;
560+
import jakarta.ws.rs.Path;
561+
562+
@RegisterRestClient
563+
@Path("/")
564+
public interface InformationService {
565+
566+
@OidcClientFilter
567+
@GET
568+
Uni<String> getUserName();
569+
570+
@OidcClientFilter
571+
@GET
572+
Uni<String> getPublicInformation();
573+
}
574+
----
575+
576+
or
577+
554578
[source,java]
555579
----
556580
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
@@ -659,6 +683,28 @@ public interface ProtectedResourceService {
659683

660684
or
661685

686+
[source,java]
687+
----
688+
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
689+
import io.quarkus.oidc.client.filter.OidcClientFilter;
690+
import jakarta.ws.rs.GET;
691+
import jakarta.ws.rs.Path;
692+
693+
@RegisterRestClient
694+
@Path("/")
695+
public interface InformationService {
696+
697+
@OidcClientFilter
698+
@GET
699+
String getUserName();
700+
701+
@GET
702+
String getPublicInformation();
703+
}
704+
----
705+
706+
or
707+
662708
[source,java]
663709
----
664710
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ NamedOidcClientFilterBuildItem registerNamedProviders(BuildProducer<ReflectiveCl
9999
}
100100

101101
// 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();
102+
final var generatedProvider = helper.getOrCreateNamedTokensProducerFor(clientName, instance);
103+
final var targetRestClient = OidcClientFilterDeploymentHelper.getTargetRestClientName(instance);
104104
namedFilterClientClasses.add(targetRestClient);
105105

106106
// register for reflection

extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/AbstractRevokedAccessTokenDevModeTest.java

Lines changed: 42 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -71,41 +71,20 @@ protected static QuarkusDevModeTest createQuarkusDevModeTest(String additionalPr
7171
.addClasses(BASE_TEST_CLASSES));
7272
}
7373

74-
@Test
75-
void verifyNamedClientHasTokenRefreshedOn401() {
74+
void verifyTokenRefreshedOn401(MyClientCategory myClientCategory) {
7675
RestAssured.given()
77-
.body(MyClientCategory.NAMED_CLIENT)
76+
.body(myClientCategory)
7877
.post(MY_CLIENT_RESOURCE_PATH)
7978
.then().statusCode(200)
8079
.body(Matchers.is(RESPONSE));
8180
// access token is revoked now
8281
RestAssured.given()
83-
.body(MyClientCategory.NAMED_CLIENT)
82+
.body(myClientCategory)
8483
.post(MY_CLIENT_RESOURCE_PATH)
8584
.then().statusCode(401);
8685
// response filter recognized 401 and told the request token to refresh the token on next request
8786
RestAssured.given()
88-
.body(MyClientCategory.NAMED_CLIENT)
89-
.post(MY_CLIENT_RESOURCE_PATH)
90-
.then().statusCode(200)
91-
.body(Matchers.is(RESPONSE));
92-
}
93-
94-
@Test
95-
void verifyDefaultClientHasTokenRefreshedOn401() {
96-
RestAssured.given()
97-
.body(MyClientCategory.DEFAULT_CLIENT)
98-
.post(MY_CLIENT_RESOURCE_PATH)
99-
.then().statusCode(200)
100-
.body(Matchers.is(RESPONSE));
101-
// access token is revoked now
102-
RestAssured.given()
103-
.body(MyClientCategory.DEFAULT_CLIENT)
104-
.post(MY_CLIENT_RESOURCE_PATH)
105-
.then().statusCode(401);
106-
// response filter recognized 401 and told the request token to refresh the token on next request
107-
RestAssured.given()
108-
.body(MyClientCategory.DEFAULT_CLIENT)
87+
.body(myClientCategory)
10988
.post(MY_CLIENT_RESOURCE_PATH)
11089
.then().statusCode(200)
11190
.body(Matchers.is(RESPONSE));
@@ -186,13 +165,19 @@ public abstract static class MyClientResource {
186165

187166
@POST
188167
public String talkToServerAndRespond(MyClientCategory clientCategory) {
189-
MyClient client = switch (clientCategory) {
190-
case NAMED_CLIENT -> myNamedClient();
191-
case DEFAULT_CLIENT -> myDefaultClient();
192-
case DEFAULT_CLIENT_WITHOUT_REFRESH -> myDefaultClientWithoutRefresh();
193-
case NAMED_CLIENT_WITHOUT_REFRESH -> myNamedClientWithoutRefresh();
168+
String named = clientCategory.named + "";
169+
return switch (clientCategory) {
170+
case NAMED_CLIENT -> myNamedClient().revokeAccessTokenAndRespond(named);
171+
case DEFAULT_CLIENT -> myDefaultClient().revokeAccessTokenAndRespond(named);
172+
case DEFAULT_CLIENT_WITHOUT_REFRESH -> myDefaultClientWithoutRefresh().revokeAccessTokenAndRespond(named);
173+
case NAMED_CLIENT_WITHOUT_REFRESH -> myNamedClientWithoutRefresh().revokeAccessTokenAndRespond(named);
174+
// calls the same endpoint as ones above, however with filter applied on individual RESTEasy client methods
175+
case NAMED_CLIENT_ANNOTATION_ON_METHOD -> myNamedClient_AnnotationOnMethod(named);
176+
case DEFAULT_CLIENT_ANNOTATION_ON_METHOD -> myDefaultClient_AnnotationOnMethod(named);
177+
case NAMED_CLIENT_MULTIPLE_METHODS -> myNamedClient_MultipleMethods(named);
178+
case DEFAULT_CLIENT_MULTIPLE_METHODS -> myDefaultClient_MultipleMethods(named);
179+
case NO_ACCESS_TOKEN -> multipleMethods_noAccessToken();
194180
};
195-
return client.revokeAccessTokenAndRespond(clientCategory.named + "");
196181
}
197182

198183
protected abstract MyClient myDefaultClient();
@@ -203,6 +188,26 @@ public String talkToServerAndRespond(MyClientCategory clientCategory) {
203188

204189
protected abstract MyClient myNamedClientWithoutRefresh();
205190

191+
protected String myDefaultClient_AnnotationOnMethod(String named) {
192+
return null;
193+
}
194+
195+
protected String myNamedClient_AnnotationOnMethod(String named) {
196+
return null;
197+
}
198+
199+
protected String myDefaultClient_MultipleMethods(String named) {
200+
return null;
201+
}
202+
203+
protected String myNamedClient_MultipleMethods(String named) {
204+
return null;
205+
}
206+
207+
protected String multipleMethods_noAccessToken() {
208+
return null;
209+
}
210+
206211
}
207212

208213
public interface MyClient {
@@ -216,7 +221,12 @@ public enum MyClientCategory {
216221
DEFAULT_CLIENT(false),
217222
NAMED_CLIENT(true),
218223
DEFAULT_CLIENT_WITHOUT_REFRESH(false),
219-
NAMED_CLIENT_WITHOUT_REFRESH(true);
224+
NAMED_CLIENT_WITHOUT_REFRESH(true),
225+
DEFAULT_CLIENT_ANNOTATION_ON_METHOD(false),
226+
NAMED_CLIENT_ANNOTATION_ON_METHOD(true),
227+
NAMED_CLIENT_MULTIPLE_METHODS(true),
228+
DEFAULT_CLIENT_MULTIPLE_METHODS(false),
229+
NO_ACCESS_TOKEN(false);
220230

221231
public final boolean named;
222232

extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientFilterRevokedAccessTokenDevModeTest.java

Lines changed: 107 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,62 @@
44

55
import jakarta.annotation.Priority;
66
import jakarta.inject.Inject;
7+
import jakarta.ws.rs.POST;
78
import jakarta.ws.rs.Path;
89
import jakarta.ws.rs.Priorities;
910

1011
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
1112
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
1213
import org.eclipse.microprofile.rest.client.inject.RestClient;
14+
import org.junit.jupiter.api.Test;
1315
import org.junit.jupiter.api.extension.RegisterExtension;
1416

1517
import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter;
1618
import io.quarkus.test.QuarkusDevModeTest;
1719
import io.quarkus.test.common.QuarkusTestResource;
1820
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
21+
import io.restassured.RestAssured;
1922

2023
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
2124
public class OidcClientFilterRevokedAccessTokenDevModeTest extends AbstractRevokedAccessTokenDevModeTest {
2225

2326
@RegisterExtension
24-
static final QuarkusDevModeTest test = createQuarkusDevModeTest(
25-
"quarkus.resteasy-client-oidc-filter.refresh-on-unauthorized=true", MyDefaultClient.class, MyNamedClient.class,
27+
static final QuarkusDevModeTest test = createQuarkusDevModeTest("""
28+
quarkus.resteasy-client-oidc-filter.refresh-on-unauthorized=true
29+
%s/mp-rest/url=http://localhost:${quarkus.http.port}
30+
%s/mp-rest/url=http://localhost:${quarkus.http.port}
31+
%s/mp-rest/url=http://localhost:${quarkus.http.port}
32+
""".formatted(MyDefaultClient_AnnotationOnMethod.class.getName(), MyNamedClient_AnnotationOnMethod.class.getName(),
33+
MyClient_MultipleMethods.class.getName()),
34+
MyDefaultClient.class, MyNamedClient.class,
2635
MyNamedClientWithoutRefresh.class, MyDefaultClientWithoutRefresh.class, MyClientResourceImpl.class,
27-
DefaultClientRefreshDisabled.class, NamedClientRefreshDisabled.class);
36+
DefaultClientRefreshDisabled.class, NamedClientRefreshDisabled.class, MyDefaultClient_AnnotationOnMethod.class,
37+
MyNamedClient_AnnotationOnMethod.class, MyClient_MultipleMethods.class);
38+
39+
@Test
40+
void verifyNamedClientHasTokenRefreshedOn401() {
41+
verifyTokenRefreshedOn401(MyClientCategory.NAMED_CLIENT_ANNOTATION_ON_METHOD);
42+
verifyTokenRefreshedOn401(MyClientCategory.NAMED_CLIENT_MULTIPLE_METHODS);
43+
verifyTokenRefreshedOn401(MyClientCategory.NAMED_CLIENT);
44+
}
45+
46+
@Test
47+
void verifyDefaultClientHasTokenRefreshedOn401() {
48+
verifyTokenRefreshedOn401(MyClientCategory.DEFAULT_CLIENT_ANNOTATION_ON_METHOD);
49+
verifyTokenRefreshedOn401(MyClientCategory.DEFAULT_CLIENT_MULTIPLE_METHODS);
50+
verifyTokenRefreshedOn401(MyClientCategory.DEFAULT_CLIENT);
51+
}
52+
53+
@Test
54+
void verifyNotAnnotatedMethodHasAccessDenied() {
55+
// this client method is not annotated with the '@OidcClientFilter' annotation, but the client server
56+
// requires authentication
57+
RestAssured.given()
58+
.body(MyClientCategory.NO_ACCESS_TOKEN)
59+
.post(MY_CLIENT_RESOURCE_PATH)
60+
.then()
61+
.statusCode(401);
62+
}
2863

2964
@RegisterRestClient
3065
@OidcClientFilter
@@ -66,6 +101,38 @@ protected Optional<String> clientId() {
66101
}
67102
}
68103

104+
@RegisterRestClient
105+
@Path(MY_SERVER_RESOURCE_PATH)
106+
public interface MyDefaultClient_AnnotationOnMethod {
107+
@OidcClientFilter
108+
@POST
109+
String revokeAccessTokenAndRespond(String named);
110+
}
111+
112+
@RegisterRestClient
113+
@Path(MY_SERVER_RESOURCE_PATH)
114+
public interface MyNamedClient_AnnotationOnMethod {
115+
@OidcClientFilter(NAMED_CLIENT)
116+
@POST
117+
String revokeAccessTokenAndRespond(String named);
118+
}
119+
120+
@RegisterRestClient
121+
@Path(MY_SERVER_RESOURCE_PATH)
122+
public interface MyClient_MultipleMethods {
123+
124+
@OidcClientFilter(NAMED_CLIENT)
125+
@POST
126+
String revokeAccessTokenAndRespond_NamedClient(String named);
127+
128+
@OidcClientFilter
129+
@POST
130+
String revokeAccessTokenAndRespond_DefaultClient(String named);
131+
132+
@POST
133+
String noAccessToken();
134+
}
135+
69136
public static class MyClientResourceImpl extends MyClientResource {
70137

71138
@Inject
@@ -84,6 +151,18 @@ public static class MyClientResourceImpl extends MyClientResource {
84151
@RestClient
85152
MyNamedClientWithoutRefresh myNamedClientWithoutRefresh;
86153

154+
@Inject
155+
@RestClient
156+
MyDefaultClient_AnnotationOnMethod myDefaultClientAnnotationOnMethod;
157+
158+
@Inject
159+
@RestClient
160+
MyNamedClient_AnnotationOnMethod myNamedClientAnnotationOnMethod;
161+
162+
@Inject
163+
@RestClient
164+
MyClient_MultipleMethods myClientMultipleMethods;
165+
87166
@Override
88167
protected MyClient myDefaultClient() {
89168
return myDefaultClient;
@@ -103,6 +182,31 @@ protected MyClient myDefaultClientWithoutRefresh() {
103182
protected MyClient myNamedClientWithoutRefresh() {
104183
return myNamedClientWithoutRefresh;
105184
}
185+
186+
@Override
187+
protected String myDefaultClient_AnnotationOnMethod(String named) {
188+
return myDefaultClientAnnotationOnMethod.revokeAccessTokenAndRespond(named);
189+
}
190+
191+
@Override
192+
protected String myNamedClient_AnnotationOnMethod(String named) {
193+
return myNamedClientAnnotationOnMethod.revokeAccessTokenAndRespond(named);
194+
}
195+
196+
@Override
197+
protected String myDefaultClient_MultipleMethods(String named) {
198+
return myClientMultipleMethods.revokeAccessTokenAndRespond_DefaultClient(named);
199+
}
200+
201+
@Override
202+
protected String myNamedClient_MultipleMethods(String named) {
203+
return myClientMultipleMethods.revokeAccessTokenAndRespond_NamedClient(named);
204+
}
205+
206+
@Override
207+
protected String multipleMethods_noAccessToken() {
208+
return myClientMultipleMethods.noAccessToken();
209+
}
106210
}
107211

108212
}

extensions/oidc-client-filter/deployment/src/test/java/io/quarkus/oidc/client/filter/OidcClientRequestFilterRevokedTokenDevModeTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.eclipse.microprofile.rest.client.annotation.RegisterProvider;
1111
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
1212
import org.eclipse.microprofile.rest.client.inject.RestClient;
13+
import org.junit.jupiter.api.Test;
1314
import org.junit.jupiter.api.extension.RegisterExtension;
1415

1516
import io.quarkus.oidc.client.filter.runtime.AbstractOidcClientRequestFilter;
@@ -26,6 +27,16 @@ public class OidcClientRequestFilterRevokedTokenDevModeTest extends AbstractRevo
2627
DefaultClientRefreshEnabled.class, NamedClientRefreshEnabled.class, DefaultClientRefreshDisabled.class,
2728
NamedClientRefreshDisabled.class);
2829

30+
@Test
31+
void verifyNamedClientHasTokenRefreshedOn401() {
32+
verifyTokenRefreshedOn401(MyClientCategory.NAMED_CLIENT);
33+
}
34+
35+
@Test
36+
void verifyDefaultClientHasTokenRefreshedOn401() {
37+
verifyTokenRefreshedOn401(MyClientCategory.DEFAULT_CLIENT);
38+
}
39+
2940
@RegisterRestClient
3041
@RegisterProvider(value = DefaultClientRefreshEnabled.class)
3142
@Path(MY_SERVER_RESOURCE_PATH)

0 commit comments

Comments
 (0)