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

Reply via email to