/** * 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.oauth2.provider; import java.net.URI; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; 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.rs.security.jose.jwt.JoseJwtConsumer; import org.apache.cxf.rs.security.oauth2.common.Client; import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; import org.apache.cxf.rs.security.oauth2.common.UserSubject; import org.apache.cxf.rs.security.oauth2.tokens.refresh.RefreshToken; import org.apache.cxf.rs.security.oauth2.utils.JwtTokenUtils; import static org.apache.cxf.jaxrs.utils.ResourceUtils.getClasspathResourceURL; public class JCacheOAuthDataProvider extends AbstractOAuthDataProvider { public static final String CLIENT_CACHE_KEY = "cxf.oauth2.client.cache"; public static final String ACCESS_TOKEN_CACHE_KEY = "cxf.oauth2.accesstoken.cache"; public static final String REFRESH_TOKEN_CACHE_KEY = "cxf.oauth2.refreshtoken.cache"; public static final String DEFAULT_CONFIG_URL = "cxf-oauth2-ehcache3.xml"; protected final CacheManager cacheManager; private final Cache<String, Client> clientCache; private Cache<String, ServerAccessToken> accessTokenCache; private Cache<String, String> jwtAccessTokenCache; private final Cache<String, RefreshToken> refreshTokenCache; private boolean storeJwtTokenKeyOnly; private JoseJwtConsumer jwtTokenConsumer; public JCacheOAuthDataProvider() throws Exception { this(false); } public JCacheOAuthDataProvider(boolean storeJwtTokenKeyOnly) throws Exception { this(DEFAULT_CONFIG_URL, BusFactory.getThreadDefaultBus(true)); } public JCacheOAuthDataProvider(String configFileURL, Bus bus) throws Exception { this(configFileURL, bus, false); } public JCacheOAuthDataProvider(String configFileURL, Bus bus, boolean storeJwtTokenKeyOnly) throws Exception { this(configFileURL, bus, CLIENT_CACHE_KEY, ACCESS_TOKEN_CACHE_KEY, REFRESH_TOKEN_CACHE_KEY, storeJwtTokenKeyOnly); } public JCacheOAuthDataProvider(String configFileURL, Bus bus, String clientCacheKey, String accessTokenCacheKey, String refreshTokenCacheKey) throws Exception { this(configFileURL, bus, clientCacheKey, accessTokenCacheKey, refreshTokenCacheKey, false); } public JCacheOAuthDataProvider(String configFileURL, Bus bus, String clientCacheKey, String accessTokenCacheKey, String refreshTokenCacheKey, boolean storeJwtTokenKeyOnly) throws Exception { cacheManager = createCacheManager(configFileURL, bus); clientCache = createCache(cacheManager, clientCacheKey, String.class, Client.class); this.storeJwtTokenKeyOnly = storeJwtTokenKeyOnly; if (storeJwtTokenKeyOnly) { jwtAccessTokenCache = createCache(cacheManager, accessTokenCacheKey, String.class, String.class); } else { accessTokenCache = createCache(cacheManager, accessTokenCacheKey, String.class, ServerAccessToken.class); } refreshTokenCache = createCache(cacheManager, refreshTokenCacheKey, String.class, RefreshToken.class); } @Override public Client doGetClient(String clientId) throws OAuthServiceException { return clientCache.get(clientId); } public void setClient(Client client) { clientCache.put(client.getClientId(), client); } @Override protected void doRemoveClient(Client c) { clientCache.remove(c.getClientId()); } @Override public List<Client> getClients(UserSubject resourceOwner) { List<Client> clients = new ArrayList<>(); for (Iterator<Cache.Entry<String, Client>> it = clientCache.iterator(); it.hasNext();) { Cache.Entry<String, Client> entry = it.next(); Client client = entry.getValue(); if (isClientMatched(client, resourceOwner)) { clients.add(client); } } return clients; } @Override public List<ServerAccessToken> getAccessTokens(Client c, UserSubject sub) { if (isUseJwtFormatForAccessTokens() && isStoreJwtTokenKeyOnly()) { return getJwtAccessTokens(c, sub); } else { return getTokens(accessTokenCache, c, sub); } } @Override public List<RefreshToken> getRefreshTokens(Client c, UserSubject sub) { return getTokens(refreshTokenCache, c, sub); } @Override public ServerAccessToken getAccessToken(String accessTokenKey) throws OAuthServiceException { if (isUseJwtFormatForAccessTokens() && isStoreJwtTokenKeyOnly()) { return getJwtAccessToken(accessTokenKey); } else { return getToken(accessTokenCache, accessTokenKey); } } @Override protected void doRevokeAccessToken(ServerAccessToken at) { accessTokenCache.remove(at.getTokenKey()); } @Override protected RefreshToken getRefreshToken(String refreshTokenKey) { return getToken(refreshTokenCache, refreshTokenKey); } @Override protected void doRevokeRefreshToken(RefreshToken rt) { refreshTokenCache.remove(rt.getTokenKey()); } @Override protected void saveAccessToken(ServerAccessToken serverToken) { if (isUseJwtFormatForAccessTokens() && isStoreJwtTokenKeyOnly()) { jwtAccessTokenCache.put(serverToken.getTokenKey(), serverToken.getTokenKey()); } else { accessTokenCache.put(serverToken.getTokenKey(), serverToken); } } @Override protected void saveRefreshToken(RefreshToken refreshToken) { refreshTokenCache.put(refreshToken.getTokenKey(), refreshToken); } @Override public void close() { clientCache.close(); refreshTokenCache.close(); if (accessTokenCache != null) { accessTokenCache.close(); } else { jwtAccessTokenCache.close(); } cacheManager.close(); } protected static <V extends ServerAccessToken> V getToken(Cache<String, V> cache, String key) { V token = cache.get(key); if (token != null && isExpired(token)) { cache.remove(key); token = null; } return token; } protected ServerAccessToken getJwtAccessToken(String key) { String jose = jwtAccessTokenCache.get(key); ServerAccessToken token = null; if (jose != null) { JoseJwtConsumer theConsumer = jwtTokenConsumer == null ? new JoseJwtConsumer() : jwtTokenConsumer; token = JwtTokenUtils.createAccessTokenFromJwt(theConsumer, jose, this, super.getJwtAccessTokenClaimMap()); if (isExpired(token)) { jwtAccessTokenCache.remove(key); token = null; } } return token; } protected static <K, V extends ServerAccessToken> List<V> getTokens(Cache<K, V> cache, Client client, UserSubject sub) { final Set<K> toRemove = new HashSet<>(); final List<V> tokens = new ArrayList<>(); for (Iterator<Cache.Entry<K, V>> it = cache.iterator(); it.hasNext();) { Cache.Entry<K, V> entry = it.next(); V token = entry.getValue(); if (!isExpired(token)) { toRemove.add(entry.getKey()); } else if (isTokenMatched(token, client, sub)) { tokens.add(token); } } cache.removeAll(toRemove); return tokens; } protected List<ServerAccessToken> getJwtAccessTokens(Client client, UserSubject sub) { final Set<String> toRemove = new HashSet<>(); final List<ServerAccessToken> tokens = new ArrayList<>(); for (Iterator<Cache.Entry<String, String>> it = jwtAccessTokenCache.iterator(); it.hasNext();) { Cache.Entry<String, String> entry = it.next(); String jose = entry.getValue(); JoseJwtConsumer theConsumer = jwtTokenConsumer == null ? new JoseJwtConsumer() : jwtTokenConsumer; ServerAccessToken token = JwtTokenUtils.createAccessTokenFromJwt(theConsumer, jose, this, super.getJwtAccessTokenClaimMap()); if (!isExpired(token)) { toRemove.add(entry.getKey()); } else if (isTokenMatched(token, client, sub)) { tokens.add(token); } } jwtAccessTokenCache.removeAll(toRemove); return tokens; } protected static boolean isExpired(ServerAccessToken token) { return System.currentTimeMillis() < (token.getIssuedAt() + token.getExpiresIn()); } protected static CacheManager createCacheManager(String configFile, Bus bus) throws Exception { if (bus == null) { bus = BusFactory.getThreadDefaultBus(true); } CachingProvider provider = Caching.getCachingProvider(); URI configFileURI; try { configFileURI = getClasspathResourceURL(configFile, JCacheOAuthDataProvider.class, bus).toURI(); } catch (Exception ex) { configFileURI = provider.getDefaultURI(); } return provider.getCacheManager(configFileURI, Thread.currentThread().getContextClassLoader()); } protected static <K, V> Cache<K, V> createCache(CacheManager cacheManager, String cacheKey, Class<K> keyType, Class<V> valueType) { Cache<K, V> cache = cacheManager.getCache(cacheKey, keyType, valueType); if (cache == null) { cache = cacheManager.createCache( cacheKey, new MutableConfiguration<K, V>() .setTypes(keyType, valueType) .setStoreByValue(true) .setStatisticsEnabled(false) ); } return cache; } public boolean isStoreJwtTokenKeyOnly() { return storeJwtTokenKeyOnly; } public void setJwtTokenConsumer(JoseJwtConsumer jwtTokenConsumer) { this.jwtTokenConsumer = jwtTokenConsumer; } }