Skip to content

Commit 097ae45

Browse files
Added interface for managing token caches
1 parent 322d28a commit 097ae45

File tree

10 files changed

+216
-51
lines changed

10 files changed

+216
-51
lines changed

modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/AuthorizationCodeHandler.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ public class AuthorizationCodeHandler extends OAuthHandler {
3737

3838
public AuthorizationCodeHandler(String tokenApiUrl, String clientId, String clientSecret,
3939
String refreshToken, String authMode, int connectionTimeout,
40-
int connectionRequestTimeout, int socketTimeout) {
40+
int connectionRequestTimeout, int socketTimeout,
41+
TokenCacheProvider tokenCacheProvider) {
4142

4243
super(tokenApiUrl, clientId, clientSecret, authMode, connectionTimeout, connectionRequestTimeout,
43-
socketTimeout);
44+
socketTimeout, tokenCacheProvider);
4445
this.refreshToken = refreshToken;
4546
}
4647

modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/ClientCredentialsHandler.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,11 @@
3434
public class ClientCredentialsHandler extends OAuthHandler {
3535

3636
public ClientCredentialsHandler(String tokenApiUrl, String clientId, String clientSecret, String authMode,
37-
int connectionTimeout, int connectionRequestTimeout, int socketTimeout) {
37+
int connectionTimeout, int connectionRequestTimeout, int socketTimeout,
38+
TokenCacheProvider tokenCacheProvider) {
3839

39-
super(tokenApiUrl, clientId, clientSecret, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout);
40+
super(tokenApiUrl, clientId, clientSecret, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout,
41+
tokenCacheProvider);
4042
}
4143

4244
@Override

modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/OAuthClient.java

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
import org.apache.http.impl.NoConnectionReuseStrategy;
4545
import org.apache.http.impl.client.CloseableHttpClient;
4646
import org.apache.http.impl.client.HttpClientBuilder;
47-
import org.apache.http.impl.client.HttpClients;
4847
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
4948
import org.apache.synapse.MessageContext;
5049
import org.apache.synapse.core.axis2.Axis2MessageContext;
@@ -100,28 +99,30 @@ public class OAuthClient {
10099
public static String generateToken(String tokenApiUrl, String payload, String credentials,
101100
MessageContext messageContext, Map<String, String> customHeaders,
102101
int connectionTimeout, int connectionRequestTimeout, int socketTimeout) throws AuthException, IOException {
103-
CloseableHttpClient httpClient = getSecureClient(tokenApiUrl, messageContext, connectionTimeout,
104-
connectionRequestTimeout, socketTimeout);
102+
105103
if (log.isDebugEnabled()) {
106104
log.debug("Initializing token generation request: [token-endpoint] " + tokenApiUrl);
107105
}
108106

109-
HttpPost httpPost = new HttpPost(tokenApiUrl);
110-
httpPost.setHeader(AuthConstants.CONTENT_TYPE_HEADER, AuthConstants.APPLICATION_X_WWW_FORM_URLENCODED);
111-
if (!(customHeaders == null || customHeaders.isEmpty())) {
112-
for (Map.Entry<String, String> entry : customHeaders.entrySet()) {
113-
httpPost.setHeader(entry.getKey(), entry.getValue());
107+
try (CloseableHttpClient httpClient = getSecureClient(tokenApiUrl, messageContext, connectionTimeout,
108+
connectionRequestTimeout, socketTimeout)) {
109+
HttpPost httpPost = new HttpPost(tokenApiUrl);
110+
httpPost.setHeader(AuthConstants.CONTENT_TYPE_HEADER, AuthConstants.APPLICATION_X_WWW_FORM_URLENCODED);
111+
if (!(customHeaders == null || customHeaders.isEmpty())) {
112+
for (Map.Entry<String, String> entry : customHeaders.entrySet()) {
113+
httpPost.setHeader(entry.getKey(), entry.getValue());
114+
}
114115
}
115-
}
116-
if (credentials != null) {
117-
httpPost.setHeader(AuthConstants.AUTHORIZATION_HEADER, AuthConstants.BASIC + credentials);
118-
}
119-
httpPost.setEntity(new StringEntity(payload));
116+
if (credentials != null) {
117+
httpPost.setHeader(AuthConstants.AUTHORIZATION_HEADER, AuthConstants.BASIC + credentials);
118+
}
119+
httpPost.setEntity(new StringEntity(payload));
120120

121-
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
122-
return extractToken(response);
123-
} finally {
124-
httpPost.releaseConnection();
121+
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
122+
return extractToken(response);
123+
} finally {
124+
httpPost.releaseConnection();
125+
}
125126
}
126127
}
127128

modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/OAuthHandler.java

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@
3434
import java.util.HashMap;
3535
import java.util.Map;
3636
import java.util.TreeMap;
37-
import java.util.concurrent.Callable;
38-
import java.util.concurrent.ExecutionException;
3937

4038
/**
4139
* This abstract class is to be used by OAuth handlers
@@ -55,9 +53,11 @@ public abstract class OAuthHandler implements AuthHandler {
5553
protected final int connectionTimeout;
5654
protected final int connectionRequestTimeout;
5755
protected final int socketTimeout;
56+
private final TokenCacheProvider tokenCacheProvider;
5857

5958
protected OAuthHandler(String tokenApiUrl, String clientId, String clientSecret, String authMode,
60-
int connectionTimeout, int connectionRequestTimeout, int socketTimeout) {
59+
int connectionTimeout, int connectionRequestTimeout, int socketTimeout,
60+
TokenCacheProvider tokenCacheProvider) {
6161

6262
this.id = OAuthUtils.getRandomOAuthHandlerID();
6363
this.tokenApiUrl = tokenApiUrl;
@@ -67,6 +67,7 @@ protected OAuthHandler(String tokenApiUrl, String clientId, String clientSecret,
6767
this.connectionTimeout = connectionTimeout;
6868
this.connectionRequestTimeout = connectionRequestTimeout;
6969
this.socketTimeout = socketTimeout;
70+
this.tokenCacheProvider = tokenCacheProvider;
7071
}
7172

7273
@Override
@@ -87,18 +88,25 @@ public void setAuthHeader(MessageContext messageContext) throws AuthException {
8788
*/
8889
private String getToken(final MessageContext messageContext) throws AuthException {
8990

90-
try {
91-
return TokenCache.getInstance().getToken(getId(messageContext), new Callable<String>() {
92-
@Override
93-
public String call() throws AuthException, IOException {
94-
return OAuthClient.generateToken(OAuthUtils.resolveExpression(tokenApiUrl, messageContext),
91+
// Check if the token is already cached
92+
String token = tokenCacheProvider.getToken(getId(messageContext));
93+
94+
synchronized (getId(messageContext).intern()) {
95+
if (StringUtils.isEmpty(token)) {
96+
// If no token found, generate a new one
97+
try {
98+
token = OAuthClient.generateToken(OAuthUtils.resolveExpression(tokenApiUrl, messageContext),
9599
buildTokenRequestPayload(messageContext), getEncodedCredentials(messageContext),
96-
messageContext, getResolvedCustomHeadersMap(customHeadersMap, messageContext), connectionTimeout,
97-
connectionRequestTimeout, socketTimeout);
100+
messageContext, getResolvedCustomHeadersMap(customHeadersMap, messageContext),
101+
connectionTimeout, connectionRequestTimeout, socketTimeout);
102+
103+
// Cache the newly generated token
104+
tokenCacheProvider.putToken(getId(messageContext), token);
105+
} catch (IOException e) {
106+
throw new AuthException("Error generating token", e);
98107
}
99-
});
100-
} catch (ExecutionException e) {
101-
throw new AuthException(e.getCause());
108+
}
109+
return token;
102110
}
103111
}
104112

@@ -133,15 +141,15 @@ public int compare(String o1, String o2) {
133141
*/
134142
public void removeTokenFromCache(MessageContext messageContext) throws AuthException {
135143

136-
TokenCache.getInstance().removeToken(getId(messageContext));
144+
tokenCacheProvider.removeToken(getId(messageContext));
137145
}
138146

139147
/**
140148
* Method to remove the token from the cache when the endpoint is destroyed.
141149
*/
142150
public void removeTokensFromCache() {
143151

144-
TokenCache.getInstance().removeTokens(id.concat("_"));
152+
tokenCacheProvider.removeTokens(id.concat("_"));
145153
}
146154

147155
/**

modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/OAuthUtils.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ private static AuthorizationCodeHandler getAuthorizationCodeHandler(OMElement au
129129
return null;
130130
}
131131
AuthorizationCodeHandler handler = new AuthorizationCodeHandler(tokenApiUrl, clientId, clientSecret,
132-
refreshToken, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout);
132+
refreshToken, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout,
133+
TokenCacheFactory.getTokenCache());
133134
if (hasRequestParameters(authCodeElement)) {
134135
Map<String, String> requestParameters = getRequestParameters(authCodeElement);
135136
if (requestParameters == null) {
@@ -170,7 +171,7 @@ private static ClientCredentialsHandler getClientCredentialsHandler(
170171
return null;
171172
}
172173
ClientCredentialsHandler handler = new ClientCredentialsHandler(tokenApiUrl, clientId, clientSecret, authMode,
173-
connectionTimeout, connectionRequestTimeout, socketTimeout);
174+
connectionTimeout, connectionRequestTimeout, socketTimeout, TokenCacheFactory.getTokenCache());
174175
if (hasRequestParameters(clientCredentialsElement)) {
175176
Map<String, String> requestParameters = getRequestParameters(clientCredentialsElement);
176177
if (requestParameters == null) {
@@ -213,7 +214,8 @@ private static PasswordCredentialsHandler getPasswordCredentialsHandler(
213214
return null;
214215
}
215216
PasswordCredentialsHandler handler = new PasswordCredentialsHandler(tokenApiUrl, clientId, clientSecret,
216-
username, password, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout);
217+
username, password, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout,
218+
TokenCacheFactory.getTokenCache());
217219
if (hasRequestParameters(passwordCredentialsElement)) {
218220
Map<String, String> requestParameters = getRequestParameters(passwordCredentialsElement);
219221
if (requestParameters == null) {

modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/PasswordCredentialsHandler.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ public class PasswordCredentialsHandler extends OAuthHandler {
3838

3939
protected PasswordCredentialsHandler(String tokenApiUrl, String clientId, String clientSecret, String username,
4040
String password, String authMode, int connectionTimeout,
41-
int connectionRequestTimeout, int socketTimeout) {
41+
int connectionRequestTimeout, int socketTimeout,
42+
TokenCacheProvider tokenCacheProvider) {
4243

43-
super(tokenApiUrl, clientId, clientSecret, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout);
44+
super(tokenApiUrl, clientId, clientSecret, authMode, connectionTimeout, connectionRequestTimeout, socketTimeout,
45+
tokenCacheProvider);
4446
this.username = username;
4547
this.password = password;
4648
}

modules/core/src/main/java/org/apache/synapse/endpoints/auth/oauth/TokenCache.java

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import org.apache.synapse.config.SynapsePropertiesLoader;
2626
import org.apache.synapse.endpoints.auth.AuthConstants;
2727

28-
import java.util.concurrent.Callable;
29-
import java.util.concurrent.ExecutionException;
3028
import java.util.concurrent.TimeUnit;
3129

3230
import static org.apache.synapse.endpoints.auth.AuthConstants.TOKEN_CACHE_TIMEOUT_PROPERTY;
@@ -35,7 +33,7 @@
3533
* Token Cache Implementation
3634
* Tokens will be invalidate after a interval of TOKEN_CACHE_TIMEOUT minutes
3735
*/
38-
public class TokenCache {
36+
public class TokenCache implements TokenCacheProvider {
3937

4038
private static final Log log = LogFactory.getLog(TokenCache.class);
4139

@@ -70,22 +68,35 @@ public static TokenCache getInstance() {
7068
}
7169

7270
/**
73-
* This method returns the value in the cache, or computes it from the specified Callable
71+
* Stores a token in the cache with the specified ID.
7472
*
75-
* @param id id of the oauth handler
76-
* @param callable to generate a new token by calling oauth server
77-
* @return Token object
73+
* @param id the unique identifier for the token
74+
* @param token the token to be cached
7875
*/
79-
public String getToken(String id, Callable<String> callable) throws ExecutionException {
76+
@Override
77+
public void putToken(String id, String token) {
8078

81-
return tokenMap.get(id, callable);
79+
tokenMap.put(id, token);
80+
}
81+
82+
/**
83+
* Retrieves a token from the cache using the specified ID.
84+
*
85+
* @param id the unique identifier for the token
86+
* @return the cached token, or {@code null} if not found
87+
*/
88+
@Override
89+
public String getToken(String id) {
90+
91+
return tokenMap.getIfPresent(id);
8292
}
8393

8494
/**
8595
* This method is called to remove the token from the cache when the token is invalid
8696
*
8797
* @param id id of the endpoint
8898
*/
99+
@Override
89100
public void removeToken(String id) {
90101

91102
tokenMap.invalidate(id);
@@ -96,6 +107,7 @@ public void removeToken(String id) {
96107
*
97108
* @param oauthHandlerId id of the OAuth handler bounded to the endpoint
98109
*/
110+
@Override
99111
public void removeTokens(String oauthHandlerId) {
100112
tokenMap.asMap().entrySet().removeIf(entry -> entry.getKey().startsWith(oauthHandlerId));
101113
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com/).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
package org.apache.synapse.endpoints.auth.oauth;
20+
21+
import org.apache.synapse.SynapseException;
22+
import org.apache.synapse.config.SynapsePropertiesLoader;
23+
24+
import java.lang.reflect.InvocationTargetException;
25+
import java.lang.reflect.Method;
26+
27+
/**
28+
* Factory class responsible for providing the appropriate implementation of the TokenCacheProvider interface.
29+
* This class manages the singleton instance of TokenCacheProvider, ensuring that it is only loaded once and reused
30+
* across the application.
31+
*/
32+
public class TokenCacheFactory {
33+
34+
/**
35+
* Singleton instance of TokenCacheProvider. This will be initialized the first time, and the same instance will be
36+
* returned on subsequent calls.
37+
*/
38+
private static TokenCacheProvider tokenCacheProvider;
39+
40+
/**
41+
* Retrieves the singleton instance of TokenCacheProvider. If the instance is not already initialized,
42+
* it attempts to load the provider class specified in the `token.cache.class` property. If the property
43+
* is not set or the class cannot be loaded, it defaults to the TokenCache implementation.
44+
*
45+
* @return the singleton instance of TokenCacheProvider
46+
* @throws SynapseException if there is an error loading the specified class
47+
*/
48+
public static TokenCacheProvider getTokenCache() {
49+
if (tokenCacheProvider != null) {
50+
return tokenCacheProvider;
51+
}
52+
53+
String classPath = SynapsePropertiesLoader.loadSynapseProperties().getProperty("token.cache.class");
54+
if (classPath != null) {
55+
tokenCacheProvider = loadTokenCacheProvider(classPath);
56+
} else {
57+
tokenCacheProvider = TokenCache.getInstance();
58+
}
59+
return tokenCacheProvider;
60+
}
61+
62+
/**
63+
* Loads the TokenCacheProvider implementation specified by the given class path.
64+
*
65+
* @param classPath the fully qualified class path of the TokenCacheProvider implementation
66+
* @return an instance of the specified TokenCacheProvider implementation
67+
* @throws SynapseException if there is an error loading the class or invoking the `getInstance` method
68+
*/
69+
private static TokenCacheProvider loadTokenCacheProvider(String classPath) {
70+
try {
71+
Class<?> clazz = Class.forName(classPath);
72+
Method getInstanceMethod = clazz.getMethod("getInstance");
73+
return (TokenCacheProvider) getInstanceMethod.invoke(null);
74+
} catch (ClassNotFoundException e) {
75+
throw new SynapseException("Error loading class: " + classPath, e);
76+
} catch (NoSuchMethodException e) {
77+
throw new SynapseException("getInstance method not found for class: " + classPath, e);
78+
} catch (InvocationTargetException | IllegalAccessException e) {
79+
throw new SynapseException("Error invoking getInstance method for class: " + classPath, e);
80+
}
81+
}
82+
}

0 commit comments

Comments
 (0)