This is an automated email from the ASF dual-hosted git repository.
oscerd pushed a commit to branch camel-4.18.x
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/camel-4.18.x by this push:
new 990fcf0d4041 [backport camel-4.18.x] CAMEL-23760: camel-oauth -
require a JWK set to verify token signatures in UserProfile (#24075)
990fcf0d4041 is described below
commit 990fcf0d404179729e735f1cf2a0627bacc00249
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Jun 17 15:44:03 2026 +0200
[backport camel-4.18.x] CAMEL-23760: camel-oauth - require a JWK set to
verify token signatures in UserProfile (#24075)
CAMEL-23760: camel-oauth - require a JWK set to verify token signatures in
UserProfile
Backport of #24032 to camel-4.18.x. The upgrade-guide entry for this
change already lives on the 4_18 guide on main (#24044).
Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
---
.../java/org/apache/camel/oauth/UserProfile.java | 21 ++---
.../org/apache/camel/oauth/UserProfileTest.java | 94 ++++++++++++++++++++++
2 files changed, 105 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 06c756ad2eda..082e85f02ba8 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
@@ -162,17 +162,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();
+ }
+}