This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 08027751b2c0 CAMEL-23760: camel-oauth - require a JWK set to verify
token signatures in UserProfile
08027751b2c0 is described below
commit 08027751b2c0e9260da49cf7352db7771822290b
Author: Andrea Cosentino <[email protected]>
AuthorDate: Tue Jun 16 09:51:46 2026 +0200
CAMEL-23760: camel-oauth - require a JWK set to verify token signatures in
UserProfile
The camel-oauth UserProfile token verification skipped the JWS signature
check when the configured JWK set was missing or empty, leaving the signature
unverified. This makes the signature check mandatory: when no JWK set is
available the token is rejected with an OAuthException rather than accepted.
Deployments with a correctly resolved JWK set are unaffected; this aligns the
legacy UserProfile path with the JwtTokenValidator SPI path, which already
fails closed on this condition.
Closes #24032
---
.../java/org/apache/camel/oauth/UserProfile.java | 21 ++---
.../org/apache/camel/oauth/UserProfileTest.java | 94 ++++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_21.adoc | 5 ++
3 files changed, 110 insertions(+), 10 deletions(-)
diff --git
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java
index 9e9524ba2d9c..71394c10ef47 100644
---
a/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java
+++
b/components/camel-oauth/src/main/java/org/apache/camel/oauth/UserProfile.java
@@ -178,17 +178,18 @@ public class UserProfile {
var signedJWT = SignedJWT.parse(token);
var keyID = signedJWT.getHeader().getKeyID();
- // Fetch Keycloak public key
+ // Resolve the signing key from the configured JWK set and verify
the token signature
var jwkSet = config.getJWKSet();
- if (!jwkSet.isEmpty()) {
- var rsaKey = (RSAKey) jwkSet.getKeyByKeyId(keyID);
- if (rsaKey == null) {
- throw new OAuthException("No matching key found for: " +
keyID);
- }
- RSAPublicKey publicKey = rsaKey.toRSAPublicKey();
- if (!signedJWT.verify(new RSASSAVerifier(publicKey))) {
- throw new OAuthException("Invalid token signature");
- }
+ if (jwkSet == null || jwkSet.isEmpty()) {
+ throw new OAuthException("Cannot verify token signature: no
JWK set available");
+ }
+ var rsaKey = (RSAKey) jwkSet.getKeyByKeyId(keyID);
+ if (rsaKey == null) {
+ throw new OAuthException("No matching key found for: " +
keyID);
+ }
+ RSAPublicKey publicKey = rsaKey.toRSAPublicKey();
+ if (!signedJWT.verify(new RSASSAVerifier(publicKey))) {
+ throw new OAuthException("Invalid token signature");
}
// Decode the payload into a JsonObject
diff --git
a/components/camel-oauth/src/test/java/org/apache/camel/oauth/UserProfileTest.java
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/UserProfileTest.java
new file mode 100644
index 000000000000..a246c259880b
--- /dev/null
+++
b/components/camel-oauth/src/test/java/org/apache/camel/oauth/UserProfileTest.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.oauth;
+
+import java.util.Date;
+
+import com.google.gson.JsonObject;
+import com.nimbusds.jose.JWSAlgorithm;
+import com.nimbusds.jose.JWSHeader;
+import com.nimbusds.jose.crypto.RSASSASigner;
+import com.nimbusds.jose.jwk.JWKSet;
+import com.nimbusds.jose.jwk.RSAKey;
+import com.nimbusds.jose.jwk.gen.RSAKeyGenerator;
+import com.nimbusds.jwt.JWTClaimsSet;
+import com.nimbusds.jwt.SignedJWT;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class UserProfileTest {
+
+ private static final String KID = "test-key-1";
+
+ private RSAKey rsaKey;
+
+ @BeforeEach
+ void setUp() throws Exception {
+ rsaKey = new RSAKeyGenerator(2048).keyID(KID).generate();
+ }
+
+ @Test
+ void accessTokenRejectedWhenJwkSetEmpty() throws Exception {
+ JsonObject json = new JsonObject();
+ json.addProperty("access_token", signedToken());
+
+ // A present-but-empty JWK set means the signature cannot be verified;
the token must not be trusted.
+ OAuthConfig config = new OAuthConfig().setClientId("my-client");
+ config.setJWKSet(new JWKSet());
+
+ assertThrows(OAuthException.class, () -> UserProfile.fromJson(config,
json));
+ }
+
+ @Test
+ void accessTokenRejectedWhenJwkSetMissing() throws Exception {
+ JsonObject json = new JsonObject();
+ json.addProperty("access_token", signedToken());
+
+ // No JWK set configured at all: still must not trust an unverified
token.
+ OAuthConfig config = new OAuthConfig().setClientId("my-client");
+
+ assertThrows(OAuthException.class, () -> UserProfile.fromJson(config,
json));
+ }
+
+ @Test
+ void accessTokenAcceptedWhenSignatureVerifies() throws Exception {
+ JsonObject json = new JsonObject();
+ json.addProperty("access_token", signedToken());
+
+ OAuthConfig config = new OAuthConfig().setClientId("my-client");
+ config.setJWKSet(new JWKSet(rsaKey.toPublicJWK()));
+
+ UserProfile profile = UserProfile.fromJson(config, json);
+ assertNotNull(profile);
+ }
+
+ private String signedToken() throws Exception {
+ JWTClaimsSet claims = new JWTClaimsSet.Builder()
+ .subject("user1")
+ .issuer("https://idp.example.com")
+ .audience("my-client")
+ .expirationTime(new Date(System.currentTimeMillis() +
300_000L))
+ .issueTime(new Date())
+ .build();
+ SignedJWT jwt = new SignedJWT(new
JWSHeader.Builder(JWSAlgorithm.RS256).keyID(KID).build(), claims);
+ jwt.sign(new RSASSASigner(rsaKey));
+ return jwt.serialize();
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
index 200cefcc894e..e8faf1620b88 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_21.adoc
@@ -944,6 +944,11 @@ behaviour.
the refresh token grant instead of the incorrect `token` parameter.
Custom test servers or identity-provider mocks that expected the old request
body should be updated.
+`UserProfile` token verification now fails closed when no JWK set is
available: a signed token can no
+longer be accepted when the configured JWK set is missing or empty, since its
signature cannot be
+verified in that case. Deployments with a correctly resolved JWK set are
unaffected; this aligns the
+legacy `UserProfile` path with the `JwtTokenValidator` SPI path.
+
=== camel-platform-http, camel-servlet, camel-jetty, camel-netty-http, and
camel-undertow
The `platform-http`, `servlet`, `jetty`, `netty-http`, and `undertow` HTTP
consumer endpoints now have an