You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rz...@apache.org on 2026/09/25 19:02:02 UTC
(tomee) 04/04: TOMEE-4029 verify tokens without kid against all configured JWKS keys
This is an automated email from the ASF dual-hosted git repository.
rzo1 pushed a commit to branch tomee-10.x
in repository https://gitbox.apache.org/repos/asf/tomee.git
commit 5fd2b10bdbb3ff68aa705d19f6b44a2a1d754b40
Author: Richard Zowalla <rz...@apache.org>
AuthorDate: Fri Sep 25 20:59:16 2026 +0200
TOMEE-4029 verify tokens without kid against all configured JWKS keys
(cherry picked from commit f951316a2eeb9f41ad5573ed682ea6f650747999)
---
.../apache/tomee/microprofile/jwt/MPJWTFilter.java | 5 +-
.../jwt/MultipleKeysWithoutKidTest.java | 84 ++++++++++++++++++++++
2 files changed, 88 insertions(+), 1 deletion(-)
diff --git a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
index 7d03074bb6..5108c49d48 100644
--- a/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
+++ b/mp-jwt/src/main/java/org/apache/tomee/microprofile/jwt/MPJWTFilter.java
@@ -442,7 +442,10 @@ public class MPJWTFilter implements Filter {
final Key key = publicKeys.values().iterator().next();
builder.setVerificationKey(key);
} else if (publicKeys.size() > 1) {
- builder.setVerificationKeyResolver(new JwksVerificationKeyResolver(asJwks(publicKeys)));
+ final JwksVerificationKeyResolver resolver = new JwksVerificationKeyResolver(asJwks(publicKeys));
+ // the kid header is optional, without it try the keys against the signature
+ resolver.setDisambiguateWithVerifySignature(true);
+ builder.setVerificationKeyResolver(resolver);
}
final Map<String, Key> decryptKeys;
diff --git a/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/MultipleKeysWithoutKidTest.java b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/MultipleKeysWithoutKidTest.java
new file mode 100644
index 0000000000..57704fa7e3
--- /dev/null
+++ b/mp-jwt/src/test/java/org/apache/tomee/microprofile/jwt/MultipleKeysWithoutKidTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.tomee.microprofile.jwt;
+
+import org.apache.tomee.microprofile.jwt.config.JWTAuthConfiguration;
+import org.eclipse.microprofile.jwt.JsonWebToken;
+import org.jose4j.jwk.RsaJsonWebKey;
+import org.jose4j.jwk.RsaJwkGenerator;
+import org.jose4j.jws.AlgorithmIdentifiers;
+import org.jose4j.jws.JsonWebSignature;
+import org.jose4j.jwt.JwtClaims;
+import org.junit.Test;
+
+import java.security.Key;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+public class MultipleKeysWithoutKidTest {
+
+ @Test
+ public void tokenWithoutKidIsVerifiedAgainstAllKeys() throws Exception {
+ final RsaJsonWebKey first = RsaJwkGenerator.generateJwk(2048);
+ final RsaJsonWebKey second = RsaJwkGenerator.generateJwk(2048);
+
+ final JsonWebToken token = MPJWTFilter.ValidateJSonWebToken.parse(sign(second), config(first, second));
+
+ assertEquals("alice", token.getName());
+ }
+
+ @Test
+ public void tokenSignedWithUnknownKeyIsRejected() throws Exception {
+ final RsaJsonWebKey first = RsaJwkGenerator.generateJwk(2048);
+ final RsaJsonWebKey second = RsaJwkGenerator.generateJwk(2048);
+ final RsaJsonWebKey unknown = RsaJwkGenerator.generateJwk(2048);
+
+ try {
+ MPJWTFilter.ValidateJSonWebToken.parse(sign(unknown), config(first, second));
+ fail("token signed with an unknown key must not be accepted");
+ } catch (final ParseException expected) {
+ // ok
+ }
+ }
+
+ private static String sign(final RsaJsonWebKey key) throws Exception {
+ final JwtClaims claims = new JwtClaims();
+ claims.setSubject("alice");
+ claims.setIssuer("https://server.example.com");
+ claims.setExpirationTimeMinutesInTheFuture(5);
+ claims.setIssuedAtToNow();
+
+ final JsonWebSignature jws = new JsonWebSignature();
+ jws.setPayload(claims.toJson());
+ jws.setKey(key.getPrivateKey());
+ jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256);
+ // no kid header on purpose
+ return jws.getCompactSerialization();
+ }
+
+ private static JWTAuthConfiguration config(final RsaJsonWebKey... keys) {
+ final Map<String, Key> publicKeys = new LinkedHashMap<>();
+ for (int i = 0; i < keys.length; i++) {
+ publicKeys.put("key" + i, keys[i].getPublicKey());
+ }
+ return new JWTAuthConfiguration(() -> publicKeys, "https://server.example.com", false, new String[0],
+ LinkedHashMap::new, "Authorization", null, null, null, null, 0);
+ }
+}