This is an automated email from the ASF dual-hosted git repository. oscerd pushed a commit to branch backport/24032-to-camel-4.14.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 188ca0c55b3df449420f5af70d5016bb180646f5 Author: Andrea Cosentino <[email protected]> AuthorDate: Wed Jun 17 11:42:32 2026 +0200 CAMEL-23760: camel-oauth - require a JWK set to verify token signatures in UserProfile Backport of #24032 to camel-4.14.x. The upgrade-guide entry for this change already lives on the 4_14 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 440aa64e5b30..55e35ca1e83a 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(); + } +}
