This is an automated email from the ASF dual-hosted git repository.

reta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/main by this push:
     new a9053c2603 CXF-8765: Option to remove Ehcache (#2196)
a9053c2603 is described below

commit a9053c26036aac80630b030c413900da7df716b1
Author: Andriy Redko <[email protected]>
AuthorDate: Sun Jan 19 10:15:18 2025 -0500

    CXF-8765: Option to remove Ehcache (#2196)
---
 rt/rs/security/sso/saml/pom.xml                    |   7 +
 .../saml/sso/jcache/JCacheTokenReplayCache.java    | 162 ++++++++++++++++++
 .../sso/state/jcache/JCacheSPStateManager.java     | 165 +++++++++++++++++++
 .../saml/src/main/resources/cxf-samlp-ehcache.xml  |  18 ++
 .../saml/src/main/resources/cxf-samlp-jcache.xml   |  74 +++++++++
 .../rs/security/saml/sso/SPStateManagerTest.java   |   3 +-
 .../rs/security/saml/sso/TokenReplayCacheTest.java |  43 +++--
 rt/ws/security/pom.xml                             |   7 +
 .../cache/jcache/CXFJCacheReplayCache.java         |  50 ++++++
 .../security/cache/jcache/JCacheReplayCache.java   | 141 ++++++++++++++++
 .../ws/security/tokenstore/TokenStoreFactory.java  |  29 +---
 .../tokenstore/jcache/JCacheTokenStore.java        | 131 +++++++++++++++
 .../tokenstore/jcache/JCacheTokenStoreFactory.java |  51 ++++++
 .../apache/cxf/ws/security/utils/JCacheUtils.java  |  73 +++++++++
 .../apache/cxf/ws/security/wss4j/WSS4JUtils.java   |   5 +
 rt/ws/security/src/main/resources/cxf-ehcache.xml  |  18 ++
 rt/ws/security/src/main/resources/cxf-jcache.xml   |  50 ++++++
 .../cxf/ws/security/tokenstore/TokenStoreTest.java |  21 ++-
 .../cxf/sts/cache/jcache/JCacheIdentityCache.java  | 182 +++++++++++++++++++++
 .../sts-core/src/main/resources/sts-ehcache.xml    |  18 ++
 .../sts/sts-core/src/main/resources/sts-jcache.xml |  40 +++++
 .../sts/cache/jcache/JCacheIdentityCacheTest.java  |  37 +++++
 .../xkms/cache/jcache/JCacheXKMSClientCache.java   | 124 ++++++++++++++
 .../apache/cxf/xkms/cache/XKMSClientCacheTest.java |  20 ++-
 .../systest/jaxrs/BookCxfContinuationStore.java    |   2 +-
 .../apache/cxf/systest/ws/cache/CachingTest.java   |  36 ++--
 26 files changed, 1451 insertions(+), 56 deletions(-)

diff --git a/rt/rs/security/sso/saml/pom.xml b/rt/rs/security/sso/saml/pom.xml
index 6a82eac384..8c70da2877 100644
--- a/rt/rs/security/sso/saml/pom.xml
+++ b/rt/rs/security/sso/saml/pom.xml
@@ -71,5 +71,12 @@
             <classifier>jakarta</classifier>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.ehcache.modules</groupId>
+            <artifactId>ehcache-107</artifactId>
+            <version>${cxf.ehcache3.version}</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
     </dependencies>
 </project>
diff --git 
a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/jcache/JCacheTokenReplayCache.java
 
b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/jcache/JCacheTokenReplayCache.java
new file mode 100644
index 0000000000..e944c36227
--- /dev/null
+++ 
b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/jcache/JCacheTokenReplayCache.java
@@ -0,0 +1,162 @@
+/**
+ * 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.cxf.rs.security.saml.sso.jcache;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.time.Instant;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.spi.CachingProvider;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.apache.cxf.rs.security.saml.sso.TokenReplayCache;
+import org.apache.wss4j.common.cache.EHCacheReplayCache;
+import org.apache.wss4j.common.cache.EHCacheValue;
+import org.apache.wss4j.common.util.Loader;
+
+/**
+ * An in-memory JCache based implementation of the TokenReplayCache interface.
+ * The default TTL is 60 minutes and the max TTL is 12 hours.
+ */
+
+public class JCacheTokenReplayCache implements TokenReplayCache<String> {
+
+    public static final String CACHE_KEY = "cxf.samlp.replay.cache";
+
+    private static final org.slf4j.Logger LOG =
+            org.slf4j.LoggerFactory.getLogger(EHCacheReplayCache.class);
+    private static final String DEFAULT_CONFIG_URL = "/cxf-samlp-jcache.xml";
+
+    private final Cache<String, EHCacheValue> cache;
+    private final CacheManager cacheManager;
+
+    public JCacheTokenReplayCache() throws URISyntaxException {
+        this(DEFAULT_CONFIG_URL, null);
+    }
+
+    public JCacheTokenReplayCache(Bus bus) throws URISyntaxException {
+        this(DEFAULT_CONFIG_URL, bus);
+    }
+
+    public JCacheTokenReplayCache(String configFile) throws URISyntaxException 
{
+        this(configFile, null);
+    }
+
+    public JCacheTokenReplayCache(String configFile, Bus bus) throws 
URISyntaxException {
+        if (bus == null) {
+            bus = BusFactory.getThreadDefaultBus(true);
+        }
+        URL configFileURL = null;
+        try {
+            configFileURL =
+                    ResourceUtils.getClasspathResourceURL(configFile, 
JCacheTokenReplayCache.class, bus);
+        } catch (Exception ex) {
+            // ignore
+        }
+
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+
+        cacheManager = cachingProvider.getCacheManager(
+            getConfigFileURL(configFileURL).toURI(), 
+            getClass().getClassLoader()); 
+
+        cache = getOrCreate(cacheManager, CACHE_KEY, String.class, 
EHCacheValue.class);
+    }
+
+    private static <K, V> Cache<K, V> getOrCreate(CacheManager cacheManager, 
String name,
+            Class<K> kclass, Class<V> vclass) {
+
+        final Cache<K, V> cache = cacheManager.getCache(name, kclass, vclass);
+        if (cache != null) {
+            return cache;
+        }
+
+        final MutableConfiguration<K, V> cacheConfiguration = new 
MutableConfiguration<>();
+        cacheConfiguration.setTypes(kclass, vclass);
+
+        return cacheManager.createCache(name, cacheConfiguration);
+    }
+
+    private URL getConfigFileURL(URL suppliedConfigFileURL) {
+        if (suppliedConfigFileURL == null) {
+            //using the default
+            try {
+                URL configFileURL = Loader.getResource(DEFAULT_CONFIG_URL);
+                if (configFileURL == null) {
+                    configFileURL = new URL(DEFAULT_CONFIG_URL);
+                }
+                return configFileURL;
+            } catch (IOException e) {
+                // Do nothing
+                LOG.debug(e.getMessage());
+            }
+        }
+        return suppliedConfigFileURL;
+    }
+
+    /**
+     * Add the given identifier to the cache. It will be cached for a default 
amount of time.
+     * @param identifier The identifier to be added
+     */
+    public void putId(String identifier) {
+        putId(identifier, null);
+    }
+
+    /**
+     * Add the given identifier to the cache to be cached for the given time
+     * @param identifier The identifier to be added
+     * @param expiry A custom expiry time for the identifier. Can be null in 
which case, the default expiry is used.
+     */
+    public void putId(String identifier, Instant expiry) {
+        if (identifier == null || "".equals(identifier)) {
+            return;
+        }
+
+        cache.put(identifier, new EHCacheValue(identifier, expiry));
+    }
+
+    /**
+     * Return true if the given identifier is contained in the cache
+     * @param identifier The identifier to check
+     */
+    public boolean contains(String identifier) {
+        if (cache == null) {
+            return false;
+        }
+        EHCacheValue element = cache.get(identifier);
+        return element != null;
+    }
+
+    public synchronized void close() {
+        if (!cacheManager.isClosed()) {
+            cacheManager.destroyCache(CACHE_KEY);
+            cacheManager.close();
+        }
+    }
+
+
+}
diff --git 
a/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/state/jcache/JCacheSPStateManager.java
 
b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/state/jcache/JCacheSPStateManager.java
new file mode 100644
index 0000000000..4932a5d0dd
--- /dev/null
+++ 
b/rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/state/jcache/JCacheSPStateManager.java
@@ -0,0 +1,165 @@
+/**
+ * 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.cxf.rs.security.saml.sso.state.jcache;
+
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.configuration.MutableConfiguration;
+import javax.cache.spi.CachingProvider;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.jaxrs.utils.ResourceUtils;
+import org.apache.cxf.rs.security.saml.sso.state.RequestState;
+import org.apache.cxf.rs.security.saml.sso.state.ResponseState;
+import org.apache.cxf.rs.security.saml.sso.state.SPStateManager;
+import org.apache.wss4j.common.util.Loader;
+
+/**
+ * An in-memory JCache based implementation of the SPStateManager interface.
+ * The default TTL is 5 minutes.
+ */
+public class JCacheSPStateManager implements SPStateManager {
+
+    public static final String REQUEST_CACHE_KEY = 
"cxf.samlp.request.state.cache";
+    public static final String RESPONSE_CACHE_KEY = 
"cxf.samlp.response.state.cache";
+    private static final String DEFAULT_CONFIG_URL = "/cxf-samlp-jcache.xml";
+    private static final org.slf4j.Logger LOG =
+            org.slf4j.LoggerFactory.getLogger(JCacheSPStateManager.class);
+
+    private final Cache<String, RequestState> requestCache;
+    private final Cache<String, ResponseState> responseCache;
+    private final CacheManager cacheManager;
+
+    public JCacheSPStateManager() throws URISyntaxException {
+        this(DEFAULT_CONFIG_URL, null);
+    }
+
+    public JCacheSPStateManager(Bus bus) throws URISyntaxException {
+        this(DEFAULT_CONFIG_URL, bus);
+    }
+
+    public JCacheSPStateManager(String configFileURL) throws 
URISyntaxException {
+        this(configFileURL, null);
+    }
+
+    public JCacheSPStateManager(String configFile, Bus bus) throws  
URISyntaxException {
+        if (bus == null) {
+            bus = BusFactory.getThreadDefaultBus(true);
+        }
+
+        URL configFileURL = null;
+        try {
+            configFileURL =
+                ResourceUtils.getClasspathResourceURL(configFile, 
JCacheSPStateManager.class, bus);
+        } catch (Exception ex) {
+            // ignore
+        }
+
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+
+        cacheManager = cachingProvider.getCacheManager(
+            getConfigFileURL(configFileURL).toURI(), 
+            getClass().getClassLoader()); 
+
+        requestCache = getOrCreate(cacheManager, REQUEST_CACHE_KEY, 
String.class, RequestState.class);
+        responseCache = getOrCreate(cacheManager, RESPONSE_CACHE_KEY, 
String.class, ResponseState.class);
+    }
+
+    private static <K, V> Cache<K, V> getOrCreate(CacheManager cacheManager, 
String name,
+            Class<K> kclass, Class<V> vclass) {
+
+        final Cache<K, V> cache = cacheManager.getCache(name, kclass, vclass);
+        if (cache != null) {
+            return cache;
+        }
+
+        final MutableConfiguration<K, V> cacheConfiguration = new 
MutableConfiguration<>();
+        cacheConfiguration.setTypes(kclass, vclass);
+
+        return cacheManager.createCache(name, cacheConfiguration);
+    }
+
+    private URL getConfigFileURL(URL suppliedConfigFileURL) {
+        if (suppliedConfigFileURL == null) {
+            //using the default
+            try {
+                URL configFileURL = Loader.getResource(DEFAULT_CONFIG_URL);
+                if (configFileURL == null) {
+                    configFileURL = new URL(DEFAULT_CONFIG_URL);
+                }
+                return configFileURL;
+            } catch (IOException e) {
+                // Do nothing
+                LOG.debug(e.getMessage());
+            }
+        }
+        return suppliedConfigFileURL;
+    }
+
+    public ResponseState getResponseState(String securityContextKey) {
+        return responseCache.get(securityContextKey);
+    }
+
+    public ResponseState removeResponseState(String securityContextKey) {
+        ResponseState responseState = getResponseState(securityContextKey);
+        if (responseState != null) {
+            responseCache.remove(securityContextKey);
+        }
+        return responseState;
+    }
+
+    public void setResponseState(String securityContextKey, ResponseState 
state) {
+        if (securityContextKey == null || "".equals(securityContextKey)) {
+            return;
+        }
+
+        responseCache.put(securityContextKey, state);
+    }
+
+    public void setRequestState(String relayState, RequestState state) {
+        if (relayState == null || "".equals(relayState)) {
+            return;
+        }
+
+        requestCache.put(relayState, state);
+    }
+
+    public RequestState removeRequestState(String relayState) {
+        RequestState state = requestCache.get(relayState);
+        if (state != null) {
+            requestCache.remove(relayState);
+        }
+        return state;
+    }
+
+    public synchronized void close() throws IOException {
+        if (!cacheManager.isClosed()) {
+            cacheManager.destroyCache(REQUEST_CACHE_KEY);
+            cacheManager.destroyCache(RESPONSE_CACHE_KEY);
+            cacheManager.close();
+        }
+    }
+
+}
diff --git a/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-ehcache.xml 
b/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-ehcache.xml
index f3b73551e4..3991aa4f91 100644
--- a/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-ehcache.xml
+++ b/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-ehcache.xml
@@ -1,4 +1,22 @@
 <?xml version="1.0"?>
+<!--
+  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.
+-->
 <config
         xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
         xmlns='http://www.ehcache.org/v3'
diff --git a/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-jcache.xml 
b/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-jcache.xml
new file mode 100644
index 0000000000..4615649554
--- /dev/null
+++ b/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-jcache.xml
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<config
+        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+        xmlns='http://www.ehcache.org/v3'
+        xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
+        xsi:schemaLocation="http://www.ehcache.org/v3 
http://www.ehcache.org/schema/ehcache-core-3.8.xsd
+            http://www.ehcache.org/v3/jsr107 
http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd";>
+
+    <service> 
+        <jsr107:defaults> 
+            <jsr107:cache name="cxf.samlp.response.state.cache" 
template="cxf.samlp.response.state.cache.template"/> 
+            <jsr107:cache name="cxf.samlp.request.state.cache" 
template="cxf.samlp.request.state.cache.template"/>
+            <jsr107:cache name="cxf.samlp.replay.cache" 
template="cxf.samlp.replay.cache.template"/>
+        </jsr107:defaults>
+    </service>
+
+    <persistence directory="${java.io.tmpdir}/ehcache/" />
+
+    <cache-template name="cxf.samlp.replay.cache.template">
+        <key-type>java.lang.String</key-type>
+        <value-type>org.apache.wss4j.common.cache.EHCacheValue</value-type>
+        <expiry>
+            <class>org.apache.wss4j.common.cache.EHCacheExpiry</class>
+        </expiry>
+        <resources>
+            <heap unit="entries">5000</heap>
+            <disk unit="MB" persistent="false">10</disk>
+        </resources>
+    </cache-template>
+
+    <cache-template name="cxf.samlp.request.state.cache.template">
+        <key-type>java.lang.String</key-type>
+        
<value-type>org.apache.cxf.rs.security.saml.sso.state.RequestState</value-type>
+        <expiry>
+            <ttl unit="seconds">300</ttl>
+        </expiry>
+        <resources>
+            <heap unit="entries">5000</heap>
+            <disk unit="MB" persistent="false">10</disk>
+        </resources>
+    </cache-template>
+
+    <cache-template name="cxf.samlp.response.state.cache.template">
+        <key-type>java.lang.String</key-type>
+        
<value-type>org.apache.cxf.rs.security.saml.sso.state.ResponseState</value-type>
+        <expiry>
+            <ttl unit="seconds">300</ttl>
+        </expiry>
+        <resources>
+            <heap unit="entries">5000</heap>
+            <disk unit="MB" persistent="false">10</disk>
+        </resources>
+    </cache-template>
+
+</config>
+
diff --git 
a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SPStateManagerTest.java
 
b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SPStateManagerTest.java
index 802b46da6f..73e588a18b 100644
--- 
a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SPStateManagerTest.java
+++ 
b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/SPStateManagerTest.java
@@ -30,6 +30,7 @@ import 
org.apache.cxf.rs.security.saml.sso.state.MemorySPStateManager;
 import org.apache.cxf.rs.security.saml.sso.state.RequestState;
 import org.apache.cxf.rs.security.saml.sso.state.ResponseState;
 import org.apache.cxf.rs.security.saml.sso.state.SPStateManager;
+import org.apache.cxf.rs.security.saml.sso.state.jcache.JCacheSPStateManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -55,7 +56,7 @@ public class SPStateManagerTest {
 
     @Parameterized.Parameters(name = "{0}")
     public static Collection<Class<? extends SPStateManager>> data() {
-        return Arrays.asList(MemorySPStateManager.class, 
EHCacheSPStateManager.class);
+        return Arrays.asList(MemorySPStateManager.class, 
EHCacheSPStateManager.class, JCacheSPStateManager.class);
     }
 
     @Before
diff --git 
a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/TokenReplayCacheTest.java
 
b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/TokenReplayCacheTest.java
index 66c8d9ced8..0b3731366b 100644
--- 
a/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/TokenReplayCacheTest.java
+++ 
b/rt/rs/security/sso/saml/src/test/java/org/apache/cxf/rs/security/saml/sso/TokenReplayCacheTest.java
@@ -21,9 +21,15 @@ package org.apache.cxf.rs.security.saml.sso;
 
 import java.time.Instant;
 import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.UUID;
 
+import org.apache.cxf.rs.security.saml.sso.jcache.JCacheTokenReplayCache;
+
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -31,12 +37,23 @@ import static org.junit.Assert.assertTrue;
 /**
  * Some unit tests for the TokenReplayCache implementations
  */
+@RunWith(value = org.junit.runners.Parameterized.class)
 public class TokenReplayCacheTest {
+    private final Class<? extends TokenReplayCache<String>> 
tokenReplayCacheClass;
+    
+    public TokenReplayCacheTest(Class<? extends TokenReplayCache<String>> 
tokenReplayCacheClass) {
+        this.tokenReplayCacheClass = tokenReplayCacheClass;
+    }
+
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<Class<? extends TokenReplayCache<String>>> data() 
{
+        return Arrays.asList(EHCacheTokenReplayCache.class, 
JCacheTokenReplayCache.class);
+    }
 
     @Test
-    public void testEhCacheTokenReplayCache() throws Exception {
+    public void testTokenReplayCacheTokenReplayCache() throws Exception {
 
-        TokenReplayCache<String> replayCache = new EHCacheTokenReplayCache();
+        TokenReplayCache<String> replayCache = createCache();
 
         testTokenReplayCacheInstance(replayCache);
 
@@ -44,16 +61,16 @@ public class TokenReplayCacheTest {
     }
 
     @Test
-    public void testEhCacheCloseCacheTwice() throws Exception {
-        TokenReplayCache<String> replayCache = new EHCacheTokenReplayCache();
+    public void testTokenReplayCacheCloseCacheTwice() throws Exception {
+        TokenReplayCache<String> replayCache = createCache();
         replayCache.close();
         replayCache.close();
     }
 
     // No expiry specified so it falls back to the default
     @Test
-    public void testEhCacheTokenReplayCacheNoExpirySpecified() throws 
Exception {
-        TokenReplayCache<String> replayCache = new EHCacheTokenReplayCache();
+    public void testTokenReplayCacheNoExpirySpecified() throws Exception {
+        TokenReplayCache<String> replayCache = createCache();
 
         String id = UUID.randomUUID().toString();
         replayCache.putId(id);
@@ -64,8 +81,8 @@ public class TokenReplayCacheTest {
 
     // The negative expiry is rejected and it falls back to the default
     @Test
-    public void testEhCacheTokenReplayCacheNegativeExpiry() throws Exception {
-        TokenReplayCache<String> replayCache = new EHCacheTokenReplayCache();
+    public void testTokenReplayCacheNegativeExpiry() throws Exception {
+        TokenReplayCache<String> replayCache = createCache();
 
         String id = UUID.randomUUID().toString();
         replayCache.putId(id, Instant.now().minusSeconds(100L));
@@ -76,8 +93,8 @@ public class TokenReplayCacheTest {
 
     // The huge expiry is rejected and it falls back to the default
     @Test
-    public void testEhCacheTokenReplayCacheHugeExpiry() throws Exception {
-        TokenReplayCache<String> replayCache = new EHCacheTokenReplayCache();
+    public void testTokenReplayCacheHugeExpiry() throws Exception {
+        TokenReplayCache<String> replayCache = createCache();
 
         String id = UUID.randomUUID().toString();
         replayCache.putId(id, Instant.now().plus(14, ChronoUnit.HOURS));
@@ -105,4 +122,8 @@ public class TokenReplayCacheTest {
         assertFalse(replayCache.contains(id));
 
     }
-}
\ No newline at end of file
+
+    private TokenReplayCache<String> createCache() throws Exception {
+        return tokenReplayCacheClass.getDeclaredConstructor().newInstance();
+    }
+}
diff --git a/rt/ws/security/pom.xml b/rt/ws/security/pom.xml
index a806e4ceca..ac714c7dee 100644
--- a/rt/ws/security/pom.xml
+++ b/rt/ws/security/pom.xml
@@ -97,6 +97,13 @@
             <classifier>jakarta</classifier>
             <scope>compile</scope>
         </dependency>
+        <dependency>
+            <groupId>org.ehcache.modules</groupId>
+            <artifactId>ehcache-107</artifactId>
+            <version>${cxf.ehcache3.version}</version>
+            <scope>provided</scope>
+            <optional>true</optional>
+        </dependency>
         <dependency>
             <groupId>org.apache.wss4j</groupId>
             <artifactId>wss4j-ws-security-dom</artifactId>
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/CXFJCacheReplayCache.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/CXFJCacheReplayCache.java
new file mode 100644
index 0000000000..e751ad8f39
--- /dev/null
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/CXFJCacheReplayCache.java
@@ -0,0 +1,50 @@
+/**
+ * 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.cxf.ws.security.cache.jcache;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.buslifecycle.BusLifeCycleListener;
+import org.apache.cxf.buslifecycle.BusLifeCycleManager;
+import org.apache.wss4j.common.ext.WSSecurityException;
+
+/**
+ * Wrap the default JCacheReplayCache in a BusLifeCycleListener, to make sure 
that
+ * the cache is shutdown correctly.
+ */
+public class CXFJCacheReplayCache extends JCacheReplayCache implements 
BusLifeCycleListener {
+    private final Bus bus;
+
+    public CXFJCacheReplayCache(String key, Bus bus) throws 
WSSecurityException {
+        super(key);
+        this.bus = bus;
+        if (bus != null) {
+            
bus.getExtension(BusLifeCycleManager.class).registerLifeCycleListener(this);
+        }
+    }
+
+    @Override
+    public void close() {
+        super.close();
+
+        if (bus != null) {
+            
bus.getExtension(BusLifeCycleManager.class).unregisterLifeCycleListener(this);
+        }
+    }
+}
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/JCacheReplayCache.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/JCacheReplayCache.java
new file mode 100644
index 0000000000..fca66a8551
--- /dev/null
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/JCacheReplayCache.java
@@ -0,0 +1,141 @@
+/**
+ * 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.cxf.ws.security.cache.jcache;
+
+import java.time.Instant;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.spi.CachingProvider;
+
+import org.apache.cxf.ws.security.utils.JCacheUtils;
+import org.apache.wss4j.common.cache.EHCacheExpiry;
+import org.apache.wss4j.common.cache.EHCacheValue;
+import org.apache.wss4j.common.cache.ReplayCache;
+import org.apache.wss4j.common.ext.WSSecurityException;
+
+/**
+ * An in-memory JCache based implementation of the ReplayCache interface.
+ * The default TTL is 60 minutes and the max TTL is 12 hours.
+ */
+class JCacheReplayCache implements ReplayCache {
+    private static final org.slf4j.Logger LOG =
+            org.slf4j.LoggerFactory.getLogger(JCacheReplayCache.class);
+
+    private final Cache<String, EHCacheValue> cache;
+    private final CacheManager cacheManager;
+    private final String key;
+
+    JCacheReplayCache(String key) throws WSSecurityException {
+        this(key, 10000);
+    }
+
+    JCacheReplayCache(String key, long heapEntries) throws WSSecurityException 
{
+        this.key = Objects.requireNonNull(key);
+
+        // Do some checking on the arguments
+        try {
+            final CachingProvider cachingProvider = 
Caching.getCachingProvider();
+            cacheManager = cachingProvider.getCacheManager(); 
+            cache = JCacheUtils.getOrCreate(cacheManager, key, String.class, 
EHCacheValue.class,
+                cacheConfiguration -> 
cacheConfiguration.setExpiryPolicyFactory(() -> new ExpiryPolicy() {
+                    @Override
+                    public Duration getExpiryForCreation() {
+                        return new Duration(TimeUnit.SECONDS, 
EHCacheExpiry.DEFAULT_TTL);
+                    }
+                    
+                    @Override
+                    public Duration getExpiryForAccess() {
+                        return null;
+                    }
+                    
+                    @Override
+                    public Duration getExpiryForUpdate() {
+                        return null;
+                    }
+                }));
+        } catch (Exception ex) {
+            LOG.error("Error configuring JCacheReplayCache: {}", 
ex.getMessage());
+            throw new 
WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex, 
"replayCacheError");
+        }
+    }
+
+    /**
+     * Add the given identifier to the cache. It will be cached for a default 
amount of time.
+     * @param identifier The identifier to be added
+     */
+    public void add(String identifier) {
+        add(identifier, null);
+    }
+
+    /**
+     * Add the given identifier to the cache to be cached for the given time
+     * @param identifier The identifier to be added
+     * @param expiry A custom expiry time for the identifier. Can be null in 
which case, the default expiry is used.
+     */
+    public void add(String identifier, Instant expiry) {
+        if (identifier == null || identifier.length() == 0) {
+            return;
+        }
+
+        cache.put(identifier, new EHCacheValue(identifier, expiry));
+    }
+
+    /**
+     * Return true if the given identifier is contained in the cache
+     * @param identifier The identifier to check
+     */
+    public boolean contains(String identifier) {
+        if (cache == null) {
+            return false;
+        }
+        EHCacheValue element = cache.get(identifier);
+        return element != null;
+    }
+
+    // Only exposed for testing
+    EHCacheValue get(String identifier) {
+        return cache.get(identifier);
+    }
+
+    @Override
+    public synchronized void close() {
+        if (!cacheManager.isClosed()) {
+            cacheManager.destroyCache(key);
+            cacheManager.close();
+        }
+    }
+
+    public void initComplete() {
+    }
+
+    public void preShutdown() {
+        close();
+    }
+
+    public void postShutdown() {
+        close();
+    }
+}
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreFactory.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreFactory.java
index 728fa3a553..436508d79d 100644
--- 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreFactory.java
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/TokenStoreFactory.java
@@ -20,36 +20,23 @@
 package org.apache.cxf.ws.security.tokenstore;
 
 import org.apache.cxf.message.Message;
+import org.apache.cxf.ws.security.tokenstore.jcache.JCacheTokenStoreFactory;
+import org.apache.cxf.ws.security.utils.JCacheUtils;
+import org.apache.wss4j.common.cache.WSS4JCacheUtil;
 
 /**
  * An abstract factory to return a TokenStore instance. It returns an 
EHCacheTokenStoreFactory
  * if EH-Cache is available. Otherwise it returns a MemoryTokenStoreFactory.
  */
 public abstract class TokenStoreFactory {
-
-    private static boolean ehCacheInstalled;
-
-    static {
-        try {
-            Class<?> cacheManagerClass = 
Class.forName("org.ehcache.CacheManager");
-            if (cacheManagerClass != null) {
-                ehCacheInstalled = true;
-            }
-        } catch (Exception e) {
-            //ignore
-        }
-    }
-
-    public static synchronized boolean isEhCacheInstalled() {
-        return ehCacheInstalled;
-    }
-
     public static TokenStoreFactory newInstance() {
-        if (isEhCacheInstalled()) {
+        if (WSS4JCacheUtil.isEhCacheInstalled()) {
             return new EHCacheTokenStoreFactory();
+        } else if (JCacheUtils.isJCacheInstalled()) {
+            return new JCacheTokenStoreFactory();
+        } else {
+            return new MemoryTokenStoreFactory();
         }
-
-        return new MemoryTokenStoreFactory();
     }
 
     public abstract TokenStore newTokenStore(String key, Message message) 
throws TokenStoreException;
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStore.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStore.java
new file mode 100644
index 0000000000..a179c6ab30
--- /dev/null
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStore.java
@@ -0,0 +1,131 @@
+/**
+ * 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.cxf.ws.security.tokenstore.jcache;
+
+import java.io.Closeable;
+import java.net.URL;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.spi.CachingProvider;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.buslifecycle.BusLifeCycleListener;
+import org.apache.cxf.buslifecycle.BusLifeCycleManager;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.ws.security.tokenstore.SecurityToken;
+import org.apache.cxf.ws.security.tokenstore.TokenStore;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreException;
+import org.apache.cxf.ws.security.utils.JCacheUtils;
+
+/**
+ * An in-memory JCache implementation of the TokenStore interface. The default 
TTL is 60 minutes
+ * and the max TTL is 12 hours.
+ */
+public class JCacheTokenStore implements TokenStore, Closeable, 
BusLifeCycleListener {
+    private final Bus bus;
+    private final Cache<String, SecurityToken> cache;
+    private final CacheManager cacheManager;
+    private final String key;
+
+    public JCacheTokenStore(String key, Bus b, URL configFileURL) throws 
TokenStoreException {
+        bus = b;
+        if (bus != null) {
+            
b.getExtension(BusLifeCycleManager.class).registerLifeCycleListener(this);
+        }
+
+        this.key = key;
+        try {
+            // Exclude the endpoint info bit added in TokenStoreUtils when 
getting the template name
+            String template = key;
+            if (template.contains("-")) {
+                template = key.substring(0, key.lastIndexOf('-'));
+            }
+
+            final CachingProvider cachingProvider = 
Caching.getCachingProvider();
+            cacheManager = 
cachingProvider.getCacheManager(configFileURL.toURI(),
+                    SecurityToken.class.getClassLoader()); 
+            cache = JCacheUtils.getOrCreate(cacheManager, key, String.class, 
SecurityToken.class);
+        } catch (Exception e) {
+            throw new TokenStoreException(e);
+        }
+    }
+
+    public void add(SecurityToken token) {
+        if (token != null && !StringUtils.isEmpty(token.getId())) {
+            cache.put(token.getId(), token);
+        }
+    }
+
+    public void add(String identifier, SecurityToken token) {
+        if (token != null && !StringUtils.isEmpty(identifier)) {
+            cache.put(identifier, token);
+        }
+    }
+
+    public void remove(String identifier) {
+        if (cache != null && !StringUtils.isEmpty(identifier)) {
+            cache.remove(identifier);
+        }
+    }
+
+    public Collection<String> getTokenIdentifiers() {
+        if (cache == null) {
+            return null;
+        }
+
+        // Not very efficient, but we are only using this method for testing
+        Set<String> keys = new HashSet<>();
+        for (Cache.Entry<String, SecurityToken> entry : cache) {
+            keys.add(entry.getKey());
+        }
+
+        return keys;
+    }
+
+    public SecurityToken getToken(String identifier) {
+        if (cache == null) {
+            return null;
+        }
+        return cache.get(identifier);
+    }
+
+    public synchronized void close() {
+        if (!cacheManager.isClosed()) {
+            cacheManager.destroyCache(key);
+            cacheManager.close();
+        }
+    }
+
+    public void initComplete() {
+    }
+
+    public void preShutdown() {
+        close();
+    }
+
+    public void postShutdown() {
+        close();
+    }
+}
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStoreFactory.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStoreFactory.java
new file mode 100644
index 0000000000..039b94912b
--- /dev/null
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStoreFactory.java
@@ -0,0 +1,51 @@
+/**
+ * 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.cxf.ws.security.tokenstore.jcache;
+
+import java.net.URL;
+
+import org.apache.cxf.message.Message;
+import org.apache.cxf.rt.security.utils.SecurityUtils;
+import org.apache.cxf.ws.security.SecurityConstants;
+import org.apache.cxf.ws.security.tokenstore.TokenStore;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreException;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreFactory;
+import org.apache.wss4j.common.util.Loader;
+
+
+/**
+ * A factory to return an JCacheTokenStore instance.
+ */
+public class JCacheTokenStoreFactory extends TokenStoreFactory {
+
+    private static final String DEFAULT_CONFIG_FILE = "cxf-jcache.xml";
+
+    @Override
+    public TokenStore newTokenStore(String key, Message message) throws 
TokenStoreException {
+        URL configFileURL = SecurityUtils.getConfigFileURL(message, 
SecurityConstants.CACHE_CONFIG_FILE,
+                DEFAULT_CONFIG_FILE);
+        if (configFileURL == null) {
+            configFileURL = 
Loader.getResource(this.getClass().getClassLoader(),
+                    DEFAULT_CONFIG_FILE);
+        }
+        return new JCacheTokenStore(key, message.getExchange().getBus(), 
configFileURL);
+    }
+
+}
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/utils/JCacheUtils.java
 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/utils/JCacheUtils.java
new file mode 100644
index 0000000000..223207695e
--- /dev/null
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/utils/JCacheUtils.java
@@ -0,0 +1,73 @@
+/**
+ * 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.cxf.ws.security.utils;
+
+import java.util.function.Function;
+import java.util.logging.Logger;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.configuration.MutableConfiguration;
+
+import org.apache.cxf.common.logging.LogUtils;
+
+public final class JCacheUtils {
+    private static final Logger LOG = LogUtils.getL7dLogger(JCacheUtils.class);
+    private static final boolean JCACHE_INSTALLED;
+
+    static {
+        boolean jcacheInstalled = false;
+        try {
+            final Class<?> caching = Class.forName("javax.cache.Caching");
+            if (caching != null) {
+                jcacheInstalled = true;
+            }
+        } catch (Exception e) {
+            LOG.fine("No JCache SPIs detected on classpath: " + 
e.getMessage());
+        }
+        JCACHE_INSTALLED = jcacheInstalled;
+    }
+
+    private JCacheUtils() {
+    }
+    
+    public static <K, V> Cache<K, V> getOrCreate(CacheManager cacheManager, 
String name, Class<K> kclass,
+            Class<V> vclass) {
+        return getOrCreate(cacheManager, name, kclass, vclass, 
Function.identity());
+    }
+
+    public static <K, V> Cache<K, V> getOrCreate(CacheManager cacheManager, 
String name, Class<K> kclass,
+            Class<V> vclass, Function<MutableConfiguration<K, V>, 
MutableConfiguration<K, V>> customizer) {
+
+        final Cache<K, V> cache = cacheManager.getCache(name, kclass, vclass);
+        if (cache != null) {
+            return cache;
+        }
+
+        MutableConfiguration<K, V> cacheConfiguration = new 
MutableConfiguration<>();
+        cacheConfiguration = 
customizer.apply(cacheConfiguration.setTypes(kclass, vclass));
+
+        return cacheManager.createCache(name, cacheConfiguration);
+    }
+
+    public static boolean isJCacheInstalled() {
+        return JCACHE_INSTALLED;
+    }
+
+}
diff --git 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/wss4j/WSS4JUtils.java 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/wss4j/WSS4JUtils.java
index b2eda68c50..df6f1b7f1f 100644
--- 
a/rt/ws/security/src/main/java/org/apache/cxf/ws/security/wss4j/WSS4JUtils.java
+++ 
b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/wss4j/WSS4JUtils.java
@@ -47,9 +47,11 @@ import org.apache.cxf.rt.security.utils.SecurityUtils;
 import org.apache.cxf.service.model.EndpointInfo;
 import org.apache.cxf.ws.security.SecurityConstants;
 import org.apache.cxf.ws.security.cache.CXFEHCacheReplayCache;
+import org.apache.cxf.ws.security.cache.jcache.CXFJCacheReplayCache;
 import org.apache.cxf.ws.security.tokenstore.SecurityToken;
 import org.apache.cxf.ws.security.tokenstore.TokenStoreException;
 import org.apache.cxf.ws.security.tokenstore.TokenStoreUtils;
+import org.apache.cxf.ws.security.utils.JCacheUtils;
 import org.apache.wss4j.common.cache.MemoryReplayCache;
 import org.apache.wss4j.common.cache.ReplayCache;
 import org.apache.wss4j.common.cache.WSS4JCacheUtil;
@@ -146,6 +148,9 @@ public final class WSS4JUtils {
                             throw new 
WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex);
                         }
                         replayCache = new CXFEHCacheReplayCache(cacheKey, bus, 
diskstoreParent);
+                    } else if (JCacheUtils.isJCacheInstalled()) {
+                        Bus bus = message.getExchange().getBus();
+                        replayCache = new CXFJCacheReplayCache(cacheKey, bus);
                     } else {
                         replayCache = new MemoryReplayCache();
                     }
diff --git a/rt/ws/security/src/main/resources/cxf-ehcache.xml 
b/rt/ws/security/src/main/resources/cxf-ehcache.xml
index eedfa20f13..c8238c712d 100644
--- a/rt/ws/security/src/main/resources/cxf-ehcache.xml
+++ b/rt/ws/security/src/main/resources/cxf-ehcache.xml
@@ -1,4 +1,22 @@
 <?xml version="1.0"?>
+<!--
+  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.
+-->
 <config
         xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
         xmlns='http://www.ehcache.org/v3'
diff --git a/rt/ws/security/src/main/resources/cxf-jcache.xml 
b/rt/ws/security/src/main/resources/cxf-jcache.xml
new file mode 100644
index 0000000000..48800ebb4c
--- /dev/null
+++ b/rt/ws/security/src/main/resources/cxf-jcache.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<config
+        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+        xmlns='http://www.ehcache.org/v3'
+        xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
+        xsi:schemaLocation="http://www.ehcache.org/v3 
http://www.ehcache.org/schema/ehcache-core-3.8.xsd
+            http://www.ehcache.org/v3/jsr107 
http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd";>
+
+    <service> 
+        <jsr107:defaults 
default-template="org.apache.cxf.ws.security.tokenstore.TokenStore.template"> 
+            <jsr107:cache 
name="org.apache.cxf.ws.security.tokenstore.TokenStore" 
template="org.apache.cxf.ws.security.tokenstore.TokenStore.template"/> 
+        </jsr107:defaults>
+    </service>
+
+    <!--
+         Security Tokens are not writable to Disk so use a memory only
+         cache with a bit larger maxEntriesLocalHeap.  However, this
+         cache is per-endpoint so not "too large" to not consume the
+         entire heap.
+    -->
+    <cache-template 
name="org.apache.cxf.ws.security.tokenstore.TokenStore.template">
+        <key-type>java.lang.String</key-type>
+        <value-type 
copier="org.ehcache.impl.copy.IdentityCopier">org.apache.cxf.ws.security.tokenstore.SecurityToken</value-type>
+        <expiry>
+            <ttl unit="seconds">3600</ttl>
+        </expiry>
+        <resources>
+            <heap unit="entries">10000</heap>
+        </resources>
+    </cache-template>
+
+</config>
diff --git 
a/rt/ws/security/src/test/java/org/apache/cxf/ws/security/tokenstore/TokenStoreTest.java
 
b/rt/ws/security/src/test/java/org/apache/cxf/ws/security/tokenstore/TokenStoreTest.java
index 506bb78a67..44707bb147 100644
--- 
a/rt/ws/security/src/test/java/org/apache/cxf/ws/security/tokenstore/TokenStoreTest.java
+++ 
b/rt/ws/security/src/test/java/org/apache/cxf/ws/security/tokenstore/TokenStoreTest.java
@@ -25,11 +25,14 @@ import org.apache.cxf.message.ExchangeImpl;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.message.MessageImpl;
 import org.apache.cxf.ws.security.SecurityConstants;
+import org.apache.cxf.ws.security.tokenstore.jcache.JCacheTokenStoreFactory;
 import org.apache.xml.security.utils.ClassLoaderUtils;
 
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -46,14 +49,13 @@ public class TokenStoreTest {
     @Parameterized.Parameters(name = "{0}")
     public static Collection<TokenStore> data() throws TokenStoreException {
         Message message = new MessageImpl();
-        message.put(
-                SecurityConstants.CACHE_CONFIG_FILE,
-                ClassLoaderUtils.getResource("cxf-ehcache.xml", 
TokenStoreTest.class)
-        );
         message.setExchange(new ExchangeImpl());
         return Arrays.asList(
                 new 
MemoryTokenStoreFactory().newTokenStore(SecurityConstants.TOKEN_STORE_CACHE_INSTANCE,
 message),
-                new 
EHCacheTokenStoreFactory().newTokenStore(SecurityConstants.TOKEN_STORE_CACHE_INSTANCE,
 message)
+                new 
EHCacheTokenStoreFactory().newTokenStore(SecurityConstants.TOKEN_STORE_CACHE_INSTANCE,
 
+                    withConfigFile(message, "cxf-ehcache.xml")),
+                new 
JCacheTokenStoreFactory().newTokenStore(SecurityConstants.TOKEN_STORE_CACHE_INSTANCE,
 
+                    withConfigFile(message, "cxf-jcache.xml"))
         );
     }
 
@@ -84,7 +86,7 @@ public class TokenStoreTest {
         store.add(token1);
         store.add(token2);
         store.add(token3);
-        assertTrue(store.getTokenIdentifiers().size() == 3);
+        assertThat(store.getTokenIdentifiers(), hasSize(3));
         store.remove(token3.getId());
         assertNull(store.getToken("test3"));
         store.remove(token1.getId());
@@ -92,4 +94,11 @@ public class TokenStoreTest {
         assertTrue(store.getTokenIdentifiers().isEmpty());
     }
 
+    private static Message withConfigFile(Message message, String configFile) {
+        message.put(
+                SecurityConstants.CACHE_CONFIG_FILE,
+                ClassLoaderUtils.getResource(configFile, TokenStoreTest.class)
+        );
+        return message;
+    }
 }
diff --git 
a/services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCache.java
 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCache.java
new file mode 100644
index 0000000000..cf58dde1ce
--- /dev/null
+++ 
b/services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCache.java
@@ -0,0 +1,182 @@
+/**
+ * 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.cxf.sts.cache.jcache;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.spi.CachingProvider;
+import javax.management.JMException;
+import javax.management.ObjectName;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.buslifecycle.BusLifeCycleListener;
+import org.apache.cxf.buslifecycle.BusLifeCycleManager;
+import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.management.InstrumentationManager;
+import org.apache.cxf.management.ManagementConstants;
+import org.apache.cxf.management.annotation.ManagedOperation;
+import org.apache.cxf.management.annotation.ManagedResource;
+import org.apache.cxf.resource.ResourceManager;
+import org.apache.cxf.sts.IdentityMapper;
+import org.apache.cxf.sts.cache.AbstractIdentityCache;
+import org.apache.cxf.sts.cache.EHCacheIdentityValue;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreFactory;
+import org.apache.cxf.ws.security.utils.JCacheUtils;
+
+/**
+ * A JCache based cache to cache identities in different realms where
+ * the relationship is of type FederateIdentity.
+ */
+@ManagedResource()
+public class JCacheIdentityCache extends AbstractIdentityCache
+    implements Closeable, BusLifeCycleListener {
+
+    private static final Logger LOG = 
LogUtils.getL7dLogger(JCacheIdentityCache.class);
+    private static final String KEY = 
"org.apache.cxf.sts.cache.EHCacheIdentityCache";
+    private Cache<String, EHCacheIdentityValue> cache;
+    private final CacheManager cacheManager;
+
+
+    public JCacheIdentityCache(
+        IdentityMapper identityMapper, Bus b
+    ) {
+        this(identityMapper, JCacheIdentityCache.class.getName(), b, null);
+    }
+    
+    public JCacheIdentityCache(IdentityMapper identityMapper, String key, Bus 
b, URL configFileURL) {
+        this(Caching.getCachingProvider(), identityMapper, key, b, 
configFileURL);
+    }
+
+    JCacheIdentityCache(CachingProvider cachingProvider, IdentityMapper 
identityMapper, 
+            String key, Bus b, URL configFileURL) {
+        super(b, identityMapper);
+        if (b != null) {
+            
b.getExtension(BusLifeCycleManager.class).registerLifeCycleListener(this);
+            InstrumentationManager im = 
b.getExtension(InstrumentationManager.class);
+            if (im != null) {
+                try {
+                    im.register(this);
+                } catch (JMException e) {
+                    LOG.log(Level.WARNING, "Registering JCacheIdentityCache 
failed.", e);
+                }
+            }
+        }
+
+        final URL xmlConfigURL = configFileURL != null ? configFileURL : 
getDefaultConfigFileURL();
+        try {
+            cacheManager = 
cachingProvider.getCacheManager(xmlConfigURL.toURI(), 
getClass().getClassLoader());
+            cache = JCacheUtils.getOrCreate(cacheManager, KEY, String.class, 
EHCacheIdentityValue.class);
+        } catch (final URISyntaxException ex) {
+            throw new IllegalStateException("Unable to convert " + 
xmlConfigURL + " to URI", ex);
+        } 
+    }
+
+    @Override
+    public void add(String user, String realm, Map<String, String> identities) 
{
+        cache.put(user + "@" + realm, new EHCacheIdentityValue(identities));
+    }
+
+    @ManagedOperation()
+    @Override
+    public Map<String, String> get(String user, String realm) {
+        EHCacheIdentityValue value = cache.get(user + "@" + realm);
+        if (value != null) {
+            return value.getValue();
+        }
+
+        return null;
+    }
+
+    @Override
+    public void remove(String user, String realm) {
+        cache.remove(user + "@" + realm);
+    }
+
+    @ManagedOperation()
+    public String getContent() {
+        return this.cache.toString();
+    }
+
+    public synchronized void close() {
+        if (!cacheManager.isClosed()) {
+            cacheManager.destroyCache(KEY);
+            cacheManager.close();
+
+            if (super.getBus() != null) {
+                
super.getBus().getExtension(BusLifeCycleManager.class).unregisterLifeCycleListener(this);
+            }
+        }
+    }
+
+    public void initComplete() {
+    }
+
+    public void preShutdown() {
+        close();
+    }
+
+    public void postShutdown() {
+        close();
+    }
+
+    private URL getDefaultConfigFileURL() {
+        URL url = null;
+        if (super.getBus() != null) {
+            ResourceManager rm = 
super.getBus().getExtension(ResourceManager.class);
+            url = rm.resolveResource("sts-jcache.xml", URL.class);
+        }
+        try {
+            if (url == null) {
+                url = ClassLoaderUtils.getResource("sts-jcache.xml", 
TokenStoreFactory.class);
+            }
+            if (url == null) {
+                url = new URL("sts-jcache.xml");
+            }
+            return url;
+        } catch (IOException e) {
+            // Do nothing
+        }
+        return null;
+    }
+
+    public ObjectName getObjectName() throws JMException {
+        StringBuilder buffer = new StringBuilder(128);
+        buffer.append(ManagementConstants.DEFAULT_DOMAIN_NAME).append(':');
+        if (super.getBus() != null) {
+            buffer.append(
+                
ManagementConstants.BUS_ID_PROP).append('=').append(super.getBus().getId()).append(',');
+        }
+        
buffer.append(ManagementConstants.TYPE_PROP).append('=').append("EHCacheIdentityCache").append(',');
+        buffer.append(ManagementConstants.NAME_PROP).append('=')
+            
.append("EHCacheIdentityCache-").append(System.identityHashCode(this));
+        return new ObjectName(buffer.toString());
+    }
+}
+
diff --git a/services/sts/sts-core/src/main/resources/sts-ehcache.xml 
b/services/sts/sts-core/src/main/resources/sts-ehcache.xml
index c1a307f44a..b5409540fc 100644
--- a/services/sts/sts-core/src/main/resources/sts-ehcache.xml
+++ b/services/sts/sts-core/src/main/resources/sts-ehcache.xml
@@ -1,4 +1,22 @@
 <?xml version="1.0"?>
+<!--
+  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.
+-->
 <config
         xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
         xmlns='http://www.ehcache.org/v3'
diff --git a/services/sts/sts-core/src/main/resources/sts-jcache.xml 
b/services/sts/sts-core/src/main/resources/sts-jcache.xml
new file mode 100644
index 0000000000..e27555b8e7
--- /dev/null
+++ b/services/sts/sts-core/src/main/resources/sts-jcache.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!--
+  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.
+-->
+<config
+        xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
+        xmlns='http://www.ehcache.org/v3'
+        xsi:schemaLocation="http://www.ehcache.org/v3 
http://www.ehcache.org/schema/ehcache-core-3.8.xsd";>
+
+    <persistence directory="${java.io.tmpdir}" />
+
+    <cache alias="org.apache.cxf.sts.cache.EHCacheIdentityCache">
+        <key-type>java.lang.String</key-type>
+        <value-type 
copier="org.ehcache.impl.copy.IdentityCopier">org.apache.cxf.sts.cache.EHCacheIdentityValue</value-type>
+        <expiry>
+            <ttl unit="seconds">3600</ttl>
+        </expiry>
+        <resources>
+            <heap unit="entries">5000</heap>
+            <disk unit="MB" persistent="false">10</disk>
+        </resources>
+    </cache>
+
+</config>
+
diff --git 
a/services/sts/sts-core/src/test/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCacheTest.java
 
b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCacheTest.java
new file mode 100644
index 0000000000..c69dee6163
--- /dev/null
+++ 
b/services/sts/sts-core/src/test/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCacheTest.java
@@ -0,0 +1,37 @@
+/**
+ * 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.cxf.sts.cache.jcache;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.sts.IdentityMapper;
+import org.apache.cxf.sts.cache.AbstractIdentityCache;
+import org.apache.cxf.sts.cache.MemoryIdentityCacheTest;
+import org.ehcache.jsr107.EhcacheCachingProvider;
+
+public class JCacheIdentityCacheTest extends MemoryIdentityCacheTest {
+
+    @Override
+    protected AbstractIdentityCache getIdentityCache(IdentityMapper mapper) {
+        Bus bus = BusFactory.getDefaultBus();
+        return new JCacheIdentityCache(new EhcacheCachingProvider(), mapper,
+            JCacheIdentityCache.class.getName(), bus, null);
+    }
+
+}
diff --git 
a/services/xkms/xkms-client/src/main/java/org/apache/cxf/xkms/cache/jcache/JCacheXKMSClientCache.java
 
b/services/xkms/xkms-client/src/main/java/org/apache/cxf/xkms/cache/jcache/JCacheXKMSClientCache.java
new file mode 100644
index 0000000000..c1c078d139
--- /dev/null
+++ 
b/services/xkms/xkms-client/src/main/java/org/apache/cxf/xkms/cache/jcache/JCacheXKMSClientCache.java
@@ -0,0 +1,124 @@
+/**
+ * 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.cxf.xkms.cache.jcache;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import javax.cache.Cache;
+import javax.cache.CacheManager;
+import javax.cache.Caching;
+import javax.cache.expiry.Duration;
+import javax.cache.expiry.ExpiryPolicy;
+import javax.cache.spi.CachingProvider;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.BusFactory;
+import org.apache.cxf.buslifecycle.BusLifeCycleListener;
+import org.apache.cxf.buslifecycle.BusLifeCycleManager;
+import org.apache.cxf.ws.security.utils.JCacheUtils;
+import org.apache.cxf.xkms.cache.XKMSCacheToken;
+import org.apache.cxf.xkms.cache.XKMSClientCache;
+import org.apache.cxf.xkms.cache.XKMSClientCacheException;
+
+/**
+ * An in-memory JCache implementation of the XKMSClientCache interface.
+ */
+public class JCacheXKMSClientCache implements XKMSClientCache, 
BusLifeCycleListener {
+    private final Cache<String, XKMSCacheToken> cache;
+    private final CacheManager cacheManager;
+    private final Bus bus;
+    private final String cacheKey;
+
+    public JCacheXKMSClientCache() throws XKMSClientCacheException {
+        this(null);
+    }
+
+    public JCacheXKMSClientCache(Bus cxfBus) throws XKMSClientCacheException {
+        if (cxfBus == null) {
+            cxfBus = BusFactory.getThreadDefaultBus(true);
+        }
+        if (cxfBus != null) {
+            
cxfBus.getExtension(BusLifeCycleManager.class).registerLifeCycleListener(this);
+        }
+
+        this.bus = cxfBus;
+        this.cacheKey = UUID.randomUUID().toString();
+
+        final CachingProvider cachingProvider = Caching.getCachingProvider();
+        cacheManager = cachingProvider.getCacheManager(); 
+
+        cache = JCacheUtils.getOrCreate(cacheManager, cacheKey, String.class, 
XKMSCacheToken.class,
+            cacheConfiguration -> cacheConfiguration.setExpiryPolicyFactory(() 
-> new ExpiryPolicy() {
+                @Override
+                public Duration getExpiryForCreation() {
+                    return new Duration(TimeUnit.SECONDS, 3600);
+                }
+
+                @Override
+                public Duration getExpiryForAccess() {
+                    return null;
+                }
+
+                @Override
+                public Duration getExpiryForUpdate() {
+                    return new Duration(TimeUnit.SECONDS, 3600);
+                }
+            }).setStoreByValue(false));
+
+    }
+
+    /**
+     * Store an XKMSCacheToken in the Cache using the given key
+     */
+    public void put(String key, XKMSCacheToken cacheToken) {
+        cache.put(key, cacheToken);
+    }
+
+    /**
+     * Get an XKMSCacheToken from the cache matching the given key. Returns 
null if there
+     * is no such XKMSCacheToken in the cache.
+     */
+    public XKMSCacheToken get(String key) {
+        return cache.get(key);
+    }
+
+    public synchronized void close() {
+        if (!cacheManager.isClosed()) {
+            cacheManager.destroyCache(cacheKey);
+            cacheManager.close();
+
+            if (bus != null) {
+                
bus.getExtension(BusLifeCycleManager.class).unregisterLifeCycleListener(this);
+            }
+        }
+    }
+
+    public void initComplete() {
+    }
+
+    public void preShutdown() {
+        close();
+    }
+
+    public void postShutdown() {
+        close();
+    }
+}
diff --git 
a/services/xkms/xkms-client/src/test/java/org/apache/cxf/xkms/cache/XKMSClientCacheTest.java
 
b/services/xkms/xkms-client/src/test/java/org/apache/cxf/xkms/cache/XKMSClientCacheTest.java
index e5dcabab6e..b9ea506e4a 100644
--- 
a/services/xkms/xkms-client/src/test/java/org/apache/cxf/xkms/cache/XKMSClientCacheTest.java
+++ 
b/services/xkms/xkms-client/src/test/java/org/apache/cxf/xkms/cache/XKMSClientCacheTest.java
@@ -22,8 +22,14 @@ package org.apache.cxf.xkms.cache;
 import java.math.BigInteger;
 import java.security.KeyStore;
 import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.Collection;
 
 import org.apache.cxf.common.classloader.ClassLoaderUtils;
+import org.apache.cxf.xkms.cache.jcache.JCacheXKMSClientCache;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -33,14 +39,15 @@ import static org.junit.Assert.assertTrue;
 /**
  * A test for the XKMSClientCache
  */
+@RunWith(value = org.junit.runners.Parameterized.class)
 public class XKMSClientCacheTest {
 
     private final XKMSClientCache cache;
-    private final X509Certificate alice;
-    private final X509Certificate bob;
+    private X509Certificate alice;
+    private X509Certificate bob;
 
-    public XKMSClientCacheTest() throws Exception {
-        cache = new EHCacheXKMSClientCache();
+    public XKMSClientCacheTest(XKMSClientCache cache) throws Exception {
+        this.cache = cache;
 
         KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
         keystore.load(ClassLoaderUtils.getResourceAsStream("keys/alice.jks",
@@ -55,6 +62,11 @@ public class XKMSClientCacheTest {
         bob = (X509Certificate)keystore.getCertificate("bob");
     }
 
+    @Parameterized.Parameters(name = "{0}")
+    public static Collection<XKMSClientCache> cache() throws 
XKMSClientCacheException {
+        return Arrays.asList(new EHCacheXKMSClientCache(), new 
JCacheXKMSClientCache());
+    }
+
     @org.junit.Test
     public void testCache() {
         assertNotNull(alice);
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookCxfContinuationStore.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookCxfContinuationStore.java
index 1dae72a693..fc20076fcf 100644
--- 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookCxfContinuationStore.java
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/BookCxfContinuationStore.java
@@ -24,6 +24,7 @@ import java.net.URI;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
@@ -40,7 +41,6 @@ import org.apache.cxf.jaxrs.ext.MessageContext;
 import org.apache.cxf.jaxrs.impl.UriInfoImpl;
 import org.apache.cxf.message.Message;
 import org.apache.cxf.phase.PhaseInterceptorChain;
-import org.ehcache.impl.internal.concurrent.ConcurrentHashMap;
 
 @Path("/bookstore")
 public class BookCxfContinuationStore {
diff --git 
a/systests/ws-security/src/test/java/org/apache/cxf/systest/ws/cache/CachingTest.java
 
b/systests/ws-security/src/test/java/org/apache/cxf/systest/ws/cache/CachingTest.java
index e15fa649ac..fbe3cbc640 100644
--- 
a/systests/ws-security/src/test/java/org/apache/cxf/systest/ws/cache/CachingTest.java
+++ 
b/systests/ws-security/src/test/java/org/apache/cxf/systest/ws/cache/CachingTest.java
@@ -20,8 +20,6 @@
 package org.apache.cxf.systest.ws.cache;
 
 import java.net.URL;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Random;
 
 import javax.xml.namespace.QName;
@@ -40,6 +38,8 @@ import 
org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
 import org.apache.cxf.ws.security.SecurityConstants;
 import org.apache.cxf.ws.security.tokenstore.EHCacheTokenStore;
 import org.apache.cxf.ws.security.tokenstore.TokenStore;
+import org.apache.cxf.ws.security.tokenstore.TokenStoreException;
+import org.apache.cxf.ws.security.tokenstore.jcache.JCacheTokenStore;
 import org.example.contract.doubleit.DoubleItPortType;
 
 import org.junit.BeforeClass;
@@ -59,11 +59,18 @@ public class CachingTest extends 
AbstractBusClientServerTestBase {
 
     private static final String NAMESPACE = 
"http://www.example.org/contract/DoubleIt";;
     private static final QName SERVICE_QNAME = new QName(NAMESPACE, 
"DoubleItService");
+    
+    @FunctionalInterface
+    private interface TokenStoreCacheFactory {
+        TokenStore create(String key, Bus b) throws TokenStoreException;
+    }
 
-    final TestParam test;
+    private final TestParam test;
+    private final TokenStoreCacheFactory factory;
 
-    public CachingTest(TestParam type) {
+    public CachingTest(TestParam type, TokenStoreCacheFactory factory) {
         this.test = type;
+        this.factory = factory;
     }
 
     @BeforeClass
@@ -76,12 +83,18 @@ public class CachingTest extends 
AbstractBusClientServerTestBase {
         );
     }
 
-    @Parameters(name = "{0}")
-    public static Collection<TestParam> data() {
-
-        return Arrays.asList(new TestParam[] {new TestParam(PORT, false),
-                                              new TestParam(PORT, true),
-        });
+    @Parameters(name = "{0},{1}")
+    public static Object[][] data() {
+        return new Object[][] {
+            {new TestParam(PORT, false), (TokenStoreCacheFactory) (String key, 
Bus b) ->
+                new EHCacheTokenStore(key, b, 
ClassLoaderUtils.getResource("cxf-ehcache.xml", CachingTest.class))},
+            {new TestParam(PORT, false), (TokenStoreCacheFactory) (String key, 
Bus b) ->
+                new JCacheTokenStore(key, b, 
ClassLoaderUtils.getResource("cxf-jcache.xml", CachingTest.class))},
+            {new TestParam(PORT, true), (TokenStoreCacheFactory) (String key, 
Bus b) ->
+                new EHCacheTokenStore(key, b, 
ClassLoaderUtils.getResource("cxf-ehcache.xml", CachingTest.class))},
+            {new TestParam(PORT, true), (TokenStoreCacheFactory) (String key, 
Bus b) ->
+                new JCacheTokenStore(key, b, 
ClassLoaderUtils.getResource("cxf-jcache.xml", CachingTest.class))}
+        };
     }
 
     @org.junit.AfterClass
@@ -174,8 +187,7 @@ public class CachingTest extends 
AbstractBusClientServerTestBase {
 
         // Create shared cache
         String cacheKey = SecurityConstants.TOKEN_STORE_CACHE_INSTANCE + '-' + 
Math.abs(new Random().nextInt());
-        TokenStore tokenStore = new EHCacheTokenStore(cacheKey, bus,
-                ClassLoaderUtils.getResource("cxf-ehcache.xml", 
this.getClass()));
+        TokenStore tokenStore = factory.create(cacheKey, bus);
         Client client = ClientProxy.getClient(port);
         
client.getEndpoint().getEndpointInfo().setProperty(SecurityConstants.TOKEN_STORE_CACHE_INSTANCE,
 tokenStore);
 

Reply via email to