Skip to content

Commit 244d7fe

Browse files
committed
Fix invalid JWT headers NPE during delayed JWK resolution
1 parent 62daf39 commit 244d7fe

File tree

4 files changed

+72
-5
lines changed

4 files changed

+72
-5
lines changed

extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/DynamicVerificationKeyResolver.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import io.quarkus.oidc.OidcTenantConfig;
2020
import io.quarkus.oidc.common.OidcRequestContextProperties;
2121
import io.quarkus.runtime.ShutdownEvent;
22+
import io.quarkus.security.AuthenticationFailedException;
2223
import io.quarkus.security.credential.TokenCredential;
2324
import io.smallrye.mutiny.Uni;
2425
import io.vertx.core.Vertx;
@@ -47,6 +48,10 @@ public DynamicVerificationKeyResolver(OidcProviderClientImpl client, OidcTenantC
4748

4849
public Uni<VerificationKeyResolver> resolve(TokenCredential tokenCred) {
4950
JsonObject headers = OidcUtils.decodeJwtHeaders(tokenCred.getToken());
51+
if (headers == null) {
52+
LOG.debug("Invalid JWT token format");
53+
return Uni.createFrom().failure(new AuthenticationFailedException());
54+
}
5055
Key key = findKeyInTheCache(headers);
5156
if (key != null) {
5257
return Uni.createFrom().item(new SingleKeyVerificationKeyResolver(key));

integration-tests/oidc/src/main/java/io/quarkus/it/keycloak/UsersResource.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ public User principalName() {
2525
return new User(identity.getPrincipal().getName());
2626
}
2727

28+
@GET
29+
@Path("/me/jwk-delayed-resolution")
30+
@RolesAllowed("user")
31+
public User principalNameJwkDelayedResolution() {
32+
return new User(identity.getPrincipal().getName());
33+
}
34+
2835
@GET
2936
@Path("/preferredUserName")
3037
@RolesAllowed("user")

integration-tests/oidc/src/main/resources/application.properties

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,23 @@ quarkus.oidc.tls.trust-store-password=password
1414
quarkus.oidc.tls.key-store-file=target/certificates/oidc-client-keystore.p12
1515
quarkus.oidc.tls.key-store-password=password
1616

17+
quarkus.oidc.jwk-delayed-resolution.auth-server-url=${quarkus.oidc.auth-server-url}
18+
quarkus.oidc.jwk-delayed-resolution.jwks.resolve-early=false
19+
quarkus.oidc.jwk-delayed-resolution.tls.tls-configuration-name=oidc-tls
20+
1721
%tls-registry.quarkus.oidc.tls.tls-configuration-name=oidc-tls
18-
%tls-registry.quarkus.tls.oidc-tls.key-store.jks.path=target/certificates/oidc-client-keystore.p12
19-
%tls-registry.quarkus.tls.oidc-tls.key-store.jks.password=password
20-
%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.path=target/certificates/oidc-client-truststore.p12
21-
%tls-registry.quarkus.tls.oidc-tls.trust-store.jks.password=password
22-
%tls-registry.quarkus.tls.oidc-tls.hostname-verification-algorithm=NONE
2322
%tls-registry.quarkus.oidc.tls.verification=
2423
%tls-registry.quarkus.oidc.tls.trust-store-file=
2524
%tls-registry.quarkus.oidc.tls.trust-store-password=
2625
%tls-registry.quarkus.oidc.tls.key-store-file=
2726
%tls-registry.quarkus.oidc.tls.key-store-password=
2827

28+
quarkus.tls.oidc-tls.key-store.jks.path=target/certificates/oidc-client-keystore.p12
29+
quarkus.tls.oidc-tls.key-store.jks.password=password
30+
quarkus.tls.oidc-tls.trust-store.jks.path=target/certificates/oidc-client-truststore.p12
31+
quarkus.tls.oidc-tls.trust-store.jks.password=password
32+
quarkus.tls.oidc-tls.hostname-verification-algorithm=NONE
33+
2934
quarkus.native.additional-build-args=-H:IncludeResources=.*\\.p12
3035

3136
quarkus.http.cors.enabled=true
@@ -44,3 +49,7 @@ quarkus.http.auth.permission.basic.auth-mechanism=basic
4449
quarkus.http.auth.permission.bearer.paths=/bearer-only
4550
quarkus.http.auth.permission.bearer.policy=authenticated
4651
quarkus.http.auth.permission.bearer.auth-mechanism=bearer
52+
53+
54+
quarkus.log.category."io.quarkus.oidc.runtime".min-level=TRACE
55+
quarkus.log.category."io.quarkus.oidc.runtime".level=TRACE

integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/AbstractBearerTokenAuthorizationTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static org.awaitility.Awaitility.await;
44
import static org.hamcrest.Matchers.equalTo;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
import static org.junit.jupiter.api.Assertions.assertNull;
57

68
import java.util.Arrays;
79
import java.util.concurrent.TimeUnit;
@@ -11,6 +13,7 @@
1113
import org.junit.jupiter.api.Test;
1214

1315
import io.quarkus.oidc.common.runtime.OidcConstants;
16+
import io.quarkus.oidc.runtime.OidcUtils;
1417
import io.quarkus.test.keycloak.client.KeycloakTestClient;
1518
import io.quarkus.test.keycloak.client.KeycloakTestClient.Tls;
1619
import io.restassured.RestAssured;
@@ -154,6 +157,49 @@ public void testVerificationFailedInvalidToken() {
154157
+ OidcConstants.RESOURCE_METADATA_WELL_KNOWN_PATH + "\""));
155158
}
156159

160+
@Test
161+
public void testVerificationFailedInvalidTokenHeaders() {
162+
String token = getAccessToken("alice");
163+
int ind = token.indexOf('.');
164+
String invalidToken = "1" + token.substring(ind);
165+
assertNull(OidcUtils.decodeJwtHeaders(invalidToken));
166+
assertNotNull(OidcUtils.decodeJwtContent(invalidToken));
167+
RestAssured.given().auth().oauth2(invalidToken)
168+
.when().get("/api/users/me").then()
169+
.statusCode(401)
170+
.header("WWW-Authenticate", equalTo("Bearer resource_metadata=\"https://localhost:8081"
171+
+ OidcConstants.RESOURCE_METADATA_WELL_KNOWN_PATH + "\""));
172+
}
173+
174+
@Test
175+
public void testVerificationEarlyJwkResolution() {
176+
String token = getAccessToken("alice");
177+
RestAssured.given().auth().oauth2(token)
178+
.when().get("/api/users/me/jwk-delayed-resolution")
179+
.then()
180+
.statusCode(200)
181+
.body("userName", equalTo("alice"));
182+
}
183+
184+
@Test
185+
public void testVerificationFailedInvalidTokenEarlyJwkResolution() {
186+
RestAssured.given().auth().oauth2("123")
187+
.when().get("/api/users/me/jwk-delayed-resolution").then()
188+
.statusCode(401);
189+
}
190+
191+
@Test
192+
public void testVerificationFailedInvalidTokenHeadersEarlyJwkResolution() {
193+
String token = getAccessToken("alice");
194+
int ind = token.indexOf('.');
195+
String invalidToken = "1" + token.substring(ind);
196+
assertNull(OidcUtils.decodeJwtHeaders(invalidToken));
197+
assertNotNull(OidcUtils.decodeJwtContent(invalidToken));
198+
RestAssured.given().auth().oauth2(invalidToken)
199+
.when().get("/api/users/me/jwk-delayed-resolution").then()
200+
.statusCode(401);
201+
}
202+
157203
//see https://github.com/quarkusio/quarkus/issues/5809
158204
@RepeatedTest(20)
159205
public void testOidcAndVertxHandler() {

0 commit comments

Comments
 (0)