From 9b1cdfdb3bb122664d189695a95413a7473b4fc2 Mon Sep 17 00:00:00 2001 From: Andriy Redko Date: Sun, 15 Dec 2024 16:29:29 -0500 Subject: [PATCH] CXF-8765: Option to remove Ehcache --- rt/rs/security/sso/saml/pom.xml | 7 + .../sso/jcache/JCacheTokenReplayCache.java | 162 ++++++++++++++++ .../state/jcache/JCacheSPStateManager.java | 165 ++++++++++++++++ .../src/main/resources/cxf-samlp-jcache.xml | 56 ++++++ .../security/saml/sso/SPStateManagerTest.java | 3 +- .../saml/sso/TokenReplayCacheTest.java | 41 +++- rt/ws/security/pom.xml | 7 + .../cache/jcache/CXFJCacheReplayCache.java | 50 +++++ .../cache/jcache/JCacheReplayCache.java | 141 ++++++++++++++ .../tokenstore/TokenStoreFactory.java | 29 +-- .../tokenstore/jcache/JCacheTokenStore.java | 131 +++++++++++++ .../jcache/JCacheTokenStoreFactory.java | 51 +++++ .../ws/security/wss4j/WSS4JCacheUtils.java | 73 +++++++ .../cxf/ws/security/wss4j/WSS4JUtils.java | 4 + .../src/main/resources/cxf-jcache.xml | 32 +++ .../security/tokenstore/TokenStoreTest.java | 21 +- .../sts/cache/jcache/JCacheIdentityCache.java | 182 ++++++++++++++++++ .../src/main/resources/sts-jcache.xml | 23 +++ .../cache/jcache/JCacheIdentityCacheTest.java | 37 ++++ .../cache/jcache/JCacheXKMSClientCache.java | 124 ++++++++++++ .../cxf/xkms/cache/XKMSClientCacheTest.java | 20 +- .../jaxrs/BookCxfContinuationStore.java | 2 +- .../cxf/systest/ws/cache/CachingTest.java | 36 ++-- 23 files changed, 1342 insertions(+), 55 deletions(-) create mode 100644 rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/jcache/JCacheTokenReplayCache.java create mode 100644 rt/rs/security/sso/saml/src/main/java/org/apache/cxf/rs/security/saml/sso/state/jcache/JCacheSPStateManager.java create mode 100644 rt/rs/security/sso/saml/src/main/resources/cxf-samlp-jcache.xml create mode 100644 rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/CXFJCacheReplayCache.java create mode 100644 rt/ws/security/src/main/java/org/apache/cxf/ws/security/cache/jcache/JCacheReplayCache.java create mode 100644 rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStore.java create mode 100644 rt/ws/security/src/main/java/org/apache/cxf/ws/security/tokenstore/jcache/JCacheTokenStoreFactory.java create mode 100644 rt/ws/security/src/main/java/org/apache/cxf/ws/security/wss4j/WSS4JCacheUtils.java create mode 100644 rt/ws/security/src/main/resources/cxf-jcache.xml create mode 100644 services/sts/sts-core/src/main/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCache.java create mode 100644 services/sts/sts-core/src/main/resources/sts-jcache.xml create mode 100644 services/sts/sts-core/src/test/java/org/apache/cxf/sts/cache/jcache/JCacheIdentityCacheTest.java create mode 100644 services/xkms/xkms-client/src/main/java/org/apache/cxf/xkms/cache/jcache/JCacheXKMSClientCache.java diff --git a/rt/rs/security/sso/saml/pom.xml b/rt/rs/security/sso/saml/pom.xml index 6a82eac384d..8c70da2877e 100644 --- a/rt/rs/security/sso/saml/pom.xml +++ b/rt/rs/security/sso/saml/pom.xml @@ -71,5 +71,12 @@ jakarta compile + + org.ehcache.modules + ehcache-107 + ${cxf.ehcache3.version} + provided + true + 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 00000000000..3f48d447e78 --- /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 EHCache implementation of the TokenReplayCache interface. + * The default TTL is 60 minutes and the max TTL is 12 hours. + */ + +public class JCacheTokenReplayCache implements TokenReplayCache { + + 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 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 Cache getOrCreate(CacheManager cacheManager, String name, + Class kclass, Class vclass) { + + final Cache cache = cacheManager.getCache(name, kclass, vclass); + if (cache != null) { + return cache; + } + + final MutableConfiguration 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 00000000000..bb8e3749e12 --- /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 EHCache 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 requestCache; + private final Cache 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 Cache getOrCreate(CacheManager cacheManager, String name, + Class kclass, Class vclass) { + + final Cache cache = cacheManager.getCache(name, kclass, vclass); + if (cache != null) { + return cache; + } + + final MutableConfiguration 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-jcache.xml b/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-jcache.xml new file mode 100644 index 00000000000..c371c55f7e8 --- /dev/null +++ b/rt/rs/security/sso/saml/src/main/resources/cxf-samlp-jcache.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + java.lang.String + org.apache.wss4j.common.cache.EHCacheValue + + org.apache.wss4j.common.cache.EHCacheExpiry + + + 5000 + 10 + + + + + java.lang.String + org.apache.cxf.rs.security.saml.sso.state.RequestState + + 300 + + + 5000 + 10 + + + + + java.lang.String + org.apache.cxf.rs.security.saml.sso.state.ResponseState + + 300 + + + 5000 + 10 + + + + + 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 802b46da6fa..73e588a18bb 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.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 SPStateManagerTest(Class stateManagerClass) { @Parameterized.Parameters(name = "{0}") public static Collection> 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 66c8d9ced86..0663d38415d 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 @@ 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 @@ /** * Some unit tests for the TokenReplayCache implementations */ +@RunWith(value = org.junit.runners.Parameterized.class) public class TokenReplayCacheTest { + private final Class> tokenReplayCacheClass; + + public TokenReplayCacheTest(Class> tokenReplayCacheClass) { + this.tokenReplayCacheClass = tokenReplayCacheClass; + } + + @Parameterized.Parameters(name = "{0}") + public static Collection>> data() { + return Arrays.asList(EHCacheTokenReplayCache.class, JCacheTokenReplayCache.class); + } @Test - public void testEhCacheTokenReplayCache() throws Exception { + public void testTokenReplayCacheTokenReplayCache() throws Exception { - TokenReplayCache replayCache = new EHCacheTokenReplayCache(); + TokenReplayCache replayCache = createCache(); testTokenReplayCacheInstance(replayCache); @@ -44,16 +61,16 @@ public void testEhCacheTokenReplayCache() throws Exception { } @Test - public void testEhCacheCloseCacheTwice() throws Exception { - TokenReplayCache replayCache = new EHCacheTokenReplayCache(); + public void testTokenReplayCacheCloseCacheTwice() throws Exception { + TokenReplayCache replayCache = createCache(); replayCache.close(); replayCache.close(); } // No expiry specified so it falls back to the default @Test - public void testEhCacheTokenReplayCacheNoExpirySpecified() throws Exception { - TokenReplayCache replayCache = new EHCacheTokenReplayCache(); + public void testTokenReplayCacheNoExpirySpecified() throws Exception { + TokenReplayCache replayCache = createCache(); String id = UUID.randomUUID().toString(); replayCache.putId(id); @@ -64,8 +81,8 @@ public void testEhCacheTokenReplayCacheNoExpirySpecified() throws Exception { // The negative expiry is rejected and it falls back to the default @Test - public void testEhCacheTokenReplayCacheNegativeExpiry() throws Exception { - TokenReplayCache replayCache = new EHCacheTokenReplayCache(); + public void testTokenReplayCacheNegativeExpiry() throws Exception { + TokenReplayCache replayCache = createCache(); String id = UUID.randomUUID().toString(); replayCache.putId(id, Instant.now().minusSeconds(100L)); @@ -76,8 +93,8 @@ public void testEhCacheTokenReplayCacheNegativeExpiry() throws Exception { // The huge expiry is rejected and it falls back to the default @Test - public void testEhCacheTokenReplayCacheHugeExpiry() throws Exception { - TokenReplayCache replayCache = new EHCacheTokenReplayCache(); + public void testTokenReplayCacheHugeExpiry() throws Exception { + TokenReplayCache replayCache = createCache(); String id = UUID.randomUUID().toString(); replayCache.putId(id, Instant.now().plus(14, ChronoUnit.HOURS)); @@ -105,4 +122,8 @@ private void testTokenReplayCacheInstance(TokenReplayCache replayCache) assertFalse(replayCache.contains(id)); } + + private TokenReplayCache createCache() throws Exception { + return tokenReplayCacheClass.getDeclaredConstructor().newInstance(); + } } \ No newline at end of file diff --git a/rt/ws/security/pom.xml b/rt/ws/security/pom.xml index a806e4cecab..ac714c7dee3 100644 --- a/rt/ws/security/pom.xml +++ b/rt/ws/security/pom.xml @@ -97,6 +97,13 @@ jakarta compile + + org.ehcache.modules + ehcache-107 + ${cxf.ehcache3.version} + provided + true + org.apache.wss4j wss4j-ws-security-dom 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 00000000000..db254b52f01 --- /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 WSS4J EHCacheReplayCache 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 00000000000..9679893939e --- /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.wss4j.WSS4JCacheUtils; +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 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 = WSS4JCacheUtils.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 728fa3a5539..3095b6778b0 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.wss4j.WSS4JCacheUtils; +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 (WSS4JCacheUtils.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 00000000000..199c5335574 --- /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.wss4j.WSS4JCacheUtils; + +/** + * 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 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 = WSS4JCacheUtils.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 getTokenIdentifiers() { + if (cache == null) { + return null; + } + + // Not very efficient, but we are only using this method for testing + Set keys = new HashSet<>(); + for (Cache.Entry 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 00000000000..039b94912bb --- /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/wss4j/WSS4JCacheUtils.java b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/wss4j/WSS4JCacheUtils.java new file mode 100644 index 00000000000..8ec5aa23a43 --- /dev/null +++ b/rt/ws/security/src/main/java/org/apache/cxf/ws/security/wss4j/WSS4JCacheUtils.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.wss4j; + +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 WSS4JCacheUtils { + private static final Logger LOG = LogUtils.getL7dLogger(WSS4JCacheUtils.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 WSS4JCacheUtils() { + } + + public static Cache getOrCreate(CacheManager cacheManager, String name, Class kclass, + Class vclass) { + return getOrCreate(cacheManager, name, kclass, vclass, Function.identity()); + } + + public static Cache getOrCreate(CacheManager cacheManager, String name, Class kclass, + Class vclass, Function, MutableConfiguration> customizer) { + + final Cache cache = cacheManager.getCache(name, kclass, vclass); + if (cache != null) { + return cache; + } + + MutableConfiguration 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 b2eda68c502..c84ff53f0d9 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,6 +47,7 @@ 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; @@ -146,6 +147,9 @@ public static ReplayCache getReplayCache( throw new WSSecurityException(WSSecurityException.ErrorCode.FAILURE, ex); } replayCache = new CXFEHCacheReplayCache(cacheKey, bus, diskstoreParent); + } else if (WSS4JCacheUtils.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-jcache.xml b/rt/ws/security/src/main/resources/cxf-jcache.xml new file mode 100644 index 00000000000..38ea3263655 --- /dev/null +++ b/rt/ws/security/src/main/resources/cxf-jcache.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + java.lang.String + org.apache.cxf.ws.security.tokenstore.SecurityToken + + 3600 + + + 10000 + + + + 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 506bb78a676..44707bb1471 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.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 TokenStoreTest(TokenStore store) { @Parameterized.Parameters(name = "{0}") public static Collection 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 void testTokenRemove() { 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 void testTokenRemove() { 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 00000000000..d65e72bda5c --- /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.wss4j.WSS4JCacheUtils; + +/** + * 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 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 = WSS4JCacheUtils.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 identities) { + cache.put(user + "@" + realm, new EHCacheIdentityValue(identities)); + } + + @ManagedOperation() + @Override + public Map 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-jcache.xml b/services/sts/sts-core/src/main/resources/sts-jcache.xml new file mode 100644 index 00000000000..e67aca3e5b0 --- /dev/null +++ b/services/sts/sts-core/src/main/resources/sts-jcache.xml @@ -0,0 +1,23 @@ + + + + + + + java.lang.String + org.apache.cxf.sts.cache.EHCacheIdentityValue + + 3600 + + + 5000 + 10 + + + + + + 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 00000000000..c69dee6163f --- /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 00000000000..b85810eb6ae --- /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.wss4j.WSS4JCacheUtils; +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 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 = WSS4JCacheUtils.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 e5dcabab6e1..b9ea506e4a7 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 @@ 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 @@ /** * 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 XKMSClientCacheTest() throws Exception { bob = (X509Certificate)keystore.getCertificate("bob"); } + @Parameterized.Parameters(name = "{0}") + public static Collection 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 1dae72a693d..fc20076fcfa 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.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.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 e15fa649acb..fbe3cbc640c 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.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 static void startServers() throws Exception { ); } - @Parameters(name = "{0}") - public static Collection 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 void testSymmetricSharedCache() throws Exception { // 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);