This is an automated email from the ASF dual-hosted git repository.
more pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/knox.git
The following commit(s) were added to refs/heads/master by this push:
new 2629b08c5 KNOX-3150 - Add support for caching JWKS keys (#1044)
2629b08c5 is described below
commit 2629b08c5ac45211717763b1ecfa26eac7118ec7
Author: Sandeep Moré <[email protected]>
AuthorDate: Wed May 14 00:24:55 2025 -0400
KNOX-3150 - Add support for caching JWKS keys (#1044)
---
.../gateway/config/impl/GatewayConfigImpl.java | 19 +++++
.../token/impl/DefaultTokenAuthorityService.java | 5 ++
.../services/token/impl/JWKSourceBuilderTest.java | 92 ++++++++++++++++++++++
.../org/apache/knox/gateway/GatewayTestConfig.java | 10 +++
.../apache/knox/gateway/config/GatewayConfig.java | 12 +++
5 files changed, 138 insertions(+)
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
index 2caef51d4..8b9f8d5ce 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/config/impl/GatewayConfigImpl.java
@@ -357,6 +357,15 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
private static final String JWKS_OUTAGE_CACHE_TTL =
GATEWAY_CONFIG_FILE_PREFIX + ".jwks.outage.cache.ttl";
private static final long JWKS_OUTAGE_CACHE_TTL_DEFAULT =
TimeUnit.HOURS.toMillis(2);
+
+ private static final String JWKS_CACHE_TTL = GATEWAY_CONFIG_FILE_PREFIX +
".jwks.cache.ttl";
+ private static final String JWKS_CACHE_REFRESH_TIMEOUT =
GATEWAY_CONFIG_FILE_PREFIX + ".jwks.cache.refresh.interval";
+ /* The default time to live of cached JWK sets, in milliseconds. Set to 20
minutes. */
+ private static final long JWKS_CACHE_TTL_DEFAULT = 20 * 60 * 1000;
+ /* The default refresh timeout of cached JWK sets, in milliseconds. Set to
15 seconds. */
+ private static final long JWKS_CACHE_REFRESH_TIMEOUT_DEFAULT = 15 * 1000;
+
+
private static final String ISSUER_IGNORE_TYPE_VALIDATION =
GATEWAY_CONFIG_FILE_PREFIX + ".token.issuers.ignore.type.validation";
//Strict-Transport Option
@@ -1627,6 +1636,16 @@ public class GatewayConfigImpl extends Configuration
implements GatewayConfig {
return getLong(JWKS_OUTAGE_CACHE_TTL, JWKS_OUTAGE_CACHE_TTL_DEFAULT);
}
+ @Override
+ public long getJwksCacheTimeToLive(){
+ return getLong(JWKS_CACHE_TTL, JWKS_CACHE_TTL_DEFAULT);
+ }
+
+ @Override
+ public long getJwksCacheRefreshTimeout(){
+ return getLong(JWKS_CACHE_REFRESH_TIMEOUT,
JWKS_CACHE_REFRESH_TIMEOUT_DEFAULT);
+ }
+
@Override
public boolean isStrictTransportEnabled() {
return getBoolean(STRICT_TRANSPORT_ENABLED,
DEFAULT_STRICT_TRANSPORT_ENABLED);
diff --git
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
index d299870e8..65e9f0ce1 100644
---
a/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
+++
b/gateway-server/src/main/java/org/apache/knox/gateway/services/token/impl/DefaultTokenAuthorityService.java
@@ -229,7 +229,12 @@ public class DefaultTokenAuthorityService implements
JWTokenAuthority, Service {
JWSAlgorithm expectedJWSAlg = JWSAlgorithm.parse(algorithm);
/* Retry one time in case of failure and cache JWKS in case there is
outage, TTL is OUTAGE_TTL */
long outageTTL = config.getJwksOutageCacheTTL();
+ long cacheTTL = config.getJwksCacheTimeToLive();
+ long cacheTimeOut = config.getJwksCacheRefreshTimeout();
+
JWKSource<SecurityContext> keySource = JWKSourceBuilder.create(new
URL(jwksurl))
+ .cache(cacheTTL, cacheTimeOut)
+ .refreshAheadCache(true)
.retrying(true)
.outageTolerant(outageTTL)
.build();
diff --git
a/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JWKSourceBuilderTest.java
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JWKSourceBuilderTest.java
new file mode 100644
index 000000000..01dc6ca41
--- /dev/null
+++
b/gateway-server/src/test/java/org/apache/knox/gateway/services/token/impl/JWKSourceBuilderTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.knox.gateway.services.token.impl;
+
+import com.nimbusds.jose.JOSEObjectType;
+import org.apache.knox.gateway.config.GatewayConfig;
+import org.apache.knox.gateway.services.security.AliasService;
+import org.apache.knox.gateway.services.security.KeystoreService;
+import org.apache.knox.gateway.services.security.token.TokenServiceException;
+import org.apache.knox.gateway.services.security.token.impl.JWT;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Test class for verifying the JWKSourceBuilder parameters in
DefaultTokenAuthorityService.
+ */
+public class JWKSourceBuilderTest {
+
+ /**
+ * Test that the cacheTTL and cacheTimeOut parameters are correctly passed
to JWKSourceBuilder.
+ * <p>
+ * This test verifies that the DefaultTokenAuthorityService correctly uses
the values from
+ * GatewayConfig when building the JWKSource.
+ */
+ @Test
+ public void testJWKSourceBuilderParameters() throws Exception {
+ // Define custom cache TTL and timeout values
+ final long customCacheTTL = 60000; // 1 minute
+ final long customCacheTimeout = 5000; // 5 seconds
+ final long customOutageTTL = 7200000; // 2 hours
+ // Create a mock GatewayConfig that returns our custom values
+ GatewayConfig config = EasyMock.createNiceMock(GatewayConfig.class);
+
EasyMock.expect(config.getJwksCacheTimeToLive()).andReturn(customCacheTTL).anyTimes();
+
EasyMock.expect(config.getJwksCacheRefreshTimeout()).andReturn(customCacheTimeout).anyTimes();
+
EasyMock.expect(config.getJwksOutageCacheTTL()).andReturn(customOutageTTL).anyTimes();
+
EasyMock.expect(config.getIssuersWithIgnoredTypeHeader()).andReturn(Collections.emptySet()).anyTimes();
+ // Mock the necessary services
+ AliasService aliasService =
EasyMock.createNiceMock(AliasService.class);
+ KeystoreService keystoreService =
EasyMock.createNiceMock(KeystoreService.class);
+ // Replay all mocks
+ EasyMock.replay(config, aliasService, keystoreService);
+ // Create the DefaultTokenAuthorityService
+ DefaultTokenAuthorityService service = new
DefaultTokenAuthorityService();
+ service.setAliasService(aliasService);
+ service.setKeystoreService(keystoreService);
+ service.init(config, new HashMap<>());
+ // Create a test JWT token
+ JWT token = EasyMock.createNiceMock(JWT.class);
+ EasyMock.expect(token.getIssuer()).andReturn("test-issuer").anyTimes();
+ EasyMock.replay(token);
+ // Define the JWK URL and algorithm
+ String jwksUrl = "https://example.com/.well-known/jwks.json";
+ String algorithm = "RS256";
+ Set<JOSEObjectType> allowedJwsTypes = new HashSet<>();
+ allowedJwsTypes.add(JOSEObjectType.JWT);
+ try {
+ // This will throw an exception because the URL doesn't exist, but
we're only
+ // interested in verifying that the correct parameters are passed
to JWKSourceBuilder
+ service.verifyToken(token, jwksUrl, algorithm, allowedJwsTypes);
+ fail("Expected TokenServiceException");
+ } catch (TokenServiceException e) {
+ // Expected exception because the URL doesn't exist
+ // Verify that the exception message indicates an attempt to
connect to the URL
+ assertTrue(e.getMessage().contains("Cannot verify token"));
+ }
+ // Verify all mocks
+ EasyMock.verify(config, aliasService, keystoreService, token);
+ }
+}
diff --git
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
index ae8c65620..c1d2f80cc 100644
---
a/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
+++
b/gateway-spi-common/src/main/java/org/apache/knox/gateway/GatewayTestConfig.java
@@ -1142,6 +1142,16 @@ public class GatewayTestConfig extends Configuration
implements GatewayConfig {
return TimeUnit.HOURS.toMillis(2);
}
+ @Override
+ public long getJwksCacheTimeToLive() {
+ return 0;
+ }
+
+ @Override
+ public long getJwksCacheRefreshTimeout() {
+ return 0;
+ }
+
@Override
public Set<String> getIssuersWithIgnoredTypeHeader() {
diff --git
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
index dd7b2441d..f92b8daec 100644
---
a/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
+++
b/gateway-spi/src/main/java/org/apache/knox/gateway/config/GatewayConfig.java
@@ -971,6 +971,18 @@ public interface GatewayConfig {
*/
long getJwksOutageCacheTTL();
+ /**
+ * The time to live of the cached JWK set, in milliseconds.
+ * @return jwks cache TTL
+ */
+ long getJwksCacheTimeToLive();
+
+ /**
+ * The cache refresh timeout, in milliseconds.
+ * @return
+ */
+ long getJwksCacheRefreshTimeout();
+
/**
* Some JWT tokens could be missing typ header.
* This config skips typ validation for tokens issued by