Modify login cache implementatation with LRU cache

(cherry picked from commit 0eb74d9f60)
4.x.x
Vigneshan Seshamany 3 years ago committed by Pahansith
parent aea460c0d5
commit 4d2f598529

@ -32,6 +32,7 @@ public class UIConfiguration {
private List<String> scopes; private List<String> scopes;
private boolean isSsoEnable; private boolean isSsoEnable;
private int sessionTimeOut; private int sessionTimeOut;
private int loginCacheCapacity;
@XmlElement(name = "AppRegistration", required=true) @XmlElement(name = "AppRegistration", required=true)
public AppRegistration getAppRegistration() { public AppRegistration getAppRegistration() {
@ -69,4 +70,13 @@ public class UIConfiguration {
public void setSessionTimeOut(int sessionTimeOut) { public void setSessionTimeOut(int sessionTimeOut) {
this.sessionTimeOut = sessionTimeOut; this.sessionTimeOut = sessionTimeOut;
} }
@XmlElement(name = "LoginCacheCapacity")
public int getLoginCacheCapacity() {
return loginCacheCapacity;
}
public void setLoginCacheCapacity(int loginCacheCapacity) {
this.loginCacheCapacity = loginCacheCapacity;
}
} }

@ -24,7 +24,7 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import io.entgra.ui.request.interceptor.beans.AuthData; import io.entgra.ui.request.interceptor.beans.AuthData;
import io.entgra.ui.request.interceptor.cache.LoginCacheManager; import io.entgra.ui.request.interceptor.cache.LoginCache;
import io.entgra.ui.request.interceptor.cache.OAuthApp; import io.entgra.ui.request.interceptor.cache.OAuthApp;
import io.entgra.ui.request.interceptor.cache.OAuthAppCacheKey; import io.entgra.ui.request.interceptor.cache.OAuthAppCacheKey;
import io.entgra.ui.request.interceptor.exceptions.LoginException; import io.entgra.ui.request.interceptor.exceptions.LoginException;
@ -39,7 +39,6 @@ import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity; import org.apache.http.entity.StringEntity;
import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HTTP;
import io.entgra.ui.request.interceptor.beans.ProxyResponse; import io.entgra.ui.request.interceptor.beans.ProxyResponse;
import org.json.JSONString;
import javax.servlet.annotation.MultipartConfig; import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet; import javax.servlet.annotation.WebServlet;
@ -80,10 +79,9 @@ public class LoginHandler extends HttpServlet {
httpSession.setMaxInactiveInterval(sessionTimeOut); httpSession.setMaxInactiveInterval(sessionTimeOut);
// Check if OAuth app cache exists. If not create a new application. // Check if OAuth app cache exists. If not create a new application.
LoginCacheManager loginCacheManager = new LoginCacheManager(); LoginCache loginCache = HandlerUtil.getLoginCache(httpSession);
loginCacheManager.initializeCacheManager();
OAuthAppCacheKey oAuthAppCacheKey = new OAuthAppCacheKey(HandlerConstants.PUBLISHER_APPLICATION_NAME, username); OAuthAppCacheKey oAuthAppCacheKey = new OAuthAppCacheKey(HandlerConstants.PUBLISHER_APPLICATION_NAME, username);
OAuthApp oAuthApp = loginCacheManager.getOAuthAppCache(oAuthAppCacheKey); OAuthApp oAuthApp = loginCache.getOAuthAppCache(oAuthAppCacheKey);
if (oAuthApp == null) { if (oAuthApp == null) {
HttpPost apiRegEndpoint = new HttpPost(gatewayUrl + HandlerConstants.APP_REG_ENDPOINT); HttpPost apiRegEndpoint = new HttpPost(gatewayUrl + HandlerConstants.APP_REG_ENDPOINT);
@ -111,8 +109,6 @@ public class LoginHandler extends HttpServlet {
clientSecret = jClientAppResultAsJsonObject.get("client_secret").getAsString(); clientSecret = jClientAppResultAsJsonObject.get("client_secret").getAsString();
encodedClientApp = Base64.getEncoder() encodedClientApp = Base64.getEncoder()
.encodeToString((clientId + HandlerConstants.COLON + clientSecret).getBytes()); .encodeToString((clientId + HandlerConstants.COLON + clientSecret).getBytes());
oAuthAppCacheKey = new OAuthAppCacheKey(HandlerConstants.PUBLISHER_APPLICATION_NAME, username);
oAuthApp = new OAuthApp( oAuthApp = new OAuthApp(
HandlerConstants.PUBLISHER_APPLICATION_NAME, HandlerConstants.PUBLISHER_APPLICATION_NAME,
username, username,
@ -120,7 +116,7 @@ public class LoginHandler extends HttpServlet {
clientSecret, clientSecret,
encodedClientApp encodedClientApp
); );
loginCacheManager.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp); loginCache.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp);
} }
if (getTokenAndPersistInSession(req, resp, clientId, clientSecret, encodedClientApp, scopes)) { if (getTokenAndPersistInSession(req, resp, clientId, clientSecret, encodedClientApp, scopes)) {

@ -23,7 +23,7 @@ import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;
import io.entgra.ui.request.interceptor.cache.LoginCacheManager; import io.entgra.ui.request.interceptor.cache.LoginCache;
import io.entgra.ui.request.interceptor.cache.OAuthApp; import io.entgra.ui.request.interceptor.cache.OAuthApp;
import io.entgra.ui.request.interceptor.cache.OAuthAppCacheKey; import io.entgra.ui.request.interceptor.cache.OAuthAppCacheKey;
import io.entgra.ui.request.interceptor.util.HandlerConstants; import io.entgra.ui.request.interceptor.util.HandlerConstants;
@ -81,9 +81,9 @@ public class SsoLoginHandler extends HttpServlet {
private JsonObject uiConfigJsonObject; private JsonObject uiConfigJsonObject;
private HttpSession httpSession; private HttpSession httpSession;
private LoginCache loginCache;
private LoginCacheManager loginCacheManager;
private OAuthApp oAuthApp; private OAuthApp oAuthApp;
private OAuthAppCacheKey oAuthAppCacheKey;
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) { protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
@ -99,13 +99,23 @@ public class SsoLoginHandler extends HttpServlet {
baseContextPath = req.getContextPath(); baseContextPath = req.getContextPath();
applicationName = baseContextPath.substring(1, baseContextPath.indexOf("-ui-request-handler")); applicationName = baseContextPath.substring(1, baseContextPath.indexOf("-ui-request-handler"));
// Check if oauth app cache is available String iotsCorePort = System.getProperty(HandlerConstants.IOT_CORE_HTTPS_PORT_ENV_VAR);
loginCacheManager = new LoginCacheManager(); if (HandlerConstants.HTTP_PROTOCOL.equals(req.getScheme())) {
loginCacheManager.initializeCacheManager(); iotsCorePort = System.getProperty(HandlerConstants.IOT_CORE_HTTP_PORT_ENV_VAR);
oAuthApp = loginCacheManager.getOAuthAppCache( }
new OAuthAppCacheKey(applicationName, adminUsername) gatewayUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_GW_HOST_ENV_VAR)
); + HandlerConstants.COLON + HandlerUtil.getGatewayPort(req.getScheme());
iotsCoreUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR)
+ HandlerConstants.COLON + iotsCorePort;
// Fetch ui config and persists in session
String uiConfigUrl = iotsCoreUrl + HandlerConstants.UI_CONFIG_ENDPOINT;
uiConfigJsonObject = HandlerUtil.getUIConfigAndPersistInSession(uiConfigUrl, gatewayUrl, httpSession, resp);
// Retrieving login cache and do a DCR if the cache is not available.
loginCache = HandlerUtil.getLoginCache(httpSession);
oAuthAppCacheKey = new OAuthAppCacheKey(applicationName, adminUsername);
oAuthApp = loginCache.getOAuthAppCache(oAuthAppCacheKey);
if (oAuthApp == null) { if (oAuthApp == null) {
dynamicClientRegistration(req, resp); dynamicClientRegistration(req, resp);
} }
@ -143,19 +153,6 @@ public class SsoLoginHandler extends HttpServlet {
*/ */
private void dynamicClientRegistration(HttpServletRequest req, HttpServletResponse resp) { private void dynamicClientRegistration(HttpServletRequest req, HttpServletResponse resp) {
try { try {
String iotsCorePort = System.getProperty(HandlerConstants.IOT_CORE_HTTPS_PORT_ENV_VAR);
if (HandlerConstants.HTTP_PROTOCOL.equals(req.getScheme())) {
iotsCorePort = System.getProperty(HandlerConstants.IOT_CORE_HTTP_PORT_ENV_VAR);
}
gatewayUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_GW_HOST_ENV_VAR)
+ HandlerConstants.COLON + HandlerUtil.getGatewayPort(req.getScheme());
iotsCoreUrl = req.getScheme() + HandlerConstants.SCHEME_SEPARATOR + System.getProperty(HandlerConstants.IOT_CORE_HOST_ENV_VAR)
+ HandlerConstants.COLON + iotsCorePort;
String uiConfigUrl = iotsCoreUrl + HandlerConstants.UI_CONFIG_ENDPOINT;
uiConfigJsonObject = HandlerUtil.getUIConfigAndPersistInSession(uiConfigUrl, gatewayUrl, httpSession, resp);
JsonArray tags = uiConfigJsonObject.get("appRegistration").getAsJsonObject().get("tags").getAsJsonArray(); JsonArray tags = uiConfigJsonObject.get("appRegistration").getAsJsonObject().get("tags").getAsJsonArray();
JsonArray scopes = uiConfigJsonObject.get("scopes").getAsJsonArray(); JsonArray scopes = uiConfigJsonObject.get("scopes").getAsJsonArray();
sessionTimeOut = Integer.parseInt(String.valueOf(uiConfigJsonObject.get("sessionTimeOut"))); sessionTimeOut = Integer.parseInt(String.valueOf(uiConfigJsonObject.get("sessionTimeOut")));
@ -191,9 +188,8 @@ public class SsoLoginHandler extends HttpServlet {
} }
// cache the oauth app credentials // cache the oauth app credentials
OAuthAppCacheKey oAuthAppCacheKey = new OAuthAppCacheKey(applicationName, adminUsername);
oAuthApp = new OAuthApp(applicationName, adminUsername, clientId, clientSecret, encodedClientApp); oAuthApp = new OAuthApp(applicationName, adminUsername, clientId, clientSecret, encodedClientApp);
loginCacheManager.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp); loginCache.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp);
} }
// Get the details of the registered application // Get the details of the registered application

@ -18,25 +18,19 @@
package io.entgra.ui.request.interceptor.cache; package io.entgra.ui.request.interceptor.cache;
import io.entgra.ui.request.interceptor.util.HandlerConstants; import java.util.LinkedHashMap;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
/** /**
* Contains necessary functions to manage oAuth app cache during login handling * Contains necessary functions to manage oAuth app cache during login handling
*/ */
public class LoginCacheManager { public class LoginCache {
private CacheManager cacheManager = null; private final LinkedHashMap<OAuthAppCacheKey, OAuthApp> cache;
private Cache<OAuthAppCacheKey, OAuthApp> cache = null; private final int capacity;
/** public LoginCache(int capacity) {
* Initialize the cache manager if it is not already initialized this.capacity = capacity;
*/ this.cache = new LinkedHashMap<>(capacity);
public void initializeCacheManager() {
cacheManager = Caching.getCacheManagerFactory().getCacheManager(HandlerConstants.LOGIN_CACHE);
} }
/** /**
@ -46,7 +40,9 @@ public class LoginCacheManager {
* @param oAuthApp - The value of the cache which contains OAuth app data * @param oAuthApp - The value of the cache which contains OAuth app data
*/ */
public void addOAuthAppToCache(OAuthAppCacheKey oAuthAppCacheKey, OAuthApp oAuthApp) { public void addOAuthAppToCache(OAuthAppCacheKey oAuthAppCacheKey, OAuthApp oAuthApp) {
cache = cacheManager.getCache(HandlerConstants.LOGIN_CACHE); if (cache.size() == capacity) {
cache.remove(cache.entrySet().iterator().next().getKey());
}
cache.put(oAuthAppCacheKey, oAuthApp); cache.put(oAuthAppCacheKey, oAuthApp);
} }
@ -57,7 +53,13 @@ public class LoginCacheManager {
* @return - Returns OAuthApp object * @return - Returns OAuthApp object
*/ */
public OAuthApp getOAuthAppCache(OAuthAppCacheKey oAuthAppCacheKey) { public OAuthApp getOAuthAppCache(OAuthAppCacheKey oAuthAppCacheKey) {
cache = cacheManager.getCache(HandlerConstants.LOGIN_CACHE); OAuthApp oAuthApp = cache.get(oAuthAppCacheKey);
return cache.get(oAuthAppCacheKey); if (oAuthApp != null) {
if (cache.size() == capacity) {
cache.remove(oAuthAppCacheKey);
cache.put(oAuthAppCacheKey, oAuthApp);
}
}
return oAuthApp;
} }
} }

@ -55,7 +55,7 @@ public class HandlerConstants {
public static final String PASSWORD_GRANT_TYPE = "password"; public static final String PASSWORD_GRANT_TYPE = "password";
public static final String JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer"; public static final String JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
public static final String PRODUCTION_KEY = "PRODUCTION"; public static final String PRODUCTION_KEY = "PRODUCTION";
public static final String LOGIN_CACHE = "LOGIN_CACHE"; public static final String LOGIN_CACHE_CAPACITY_KEY = "loginCacheCapacity";
public static final String SCHEME_SEPARATOR = "://"; public static final String SCHEME_SEPARATOR = "://";
public static final String COLON = ":"; public static final String COLON = ":";

@ -23,13 +23,17 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import io.entgra.ui.request.interceptor.beans.AuthData;
import io.entgra.ui.request.interceptor.cache.LoginCache;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.apache.http.Consts; import org.apache.http.Consts;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus; import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.NoopHostnameVerifier; import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.entity.ContentType; import org.apache.http.entity.ContentType;
@ -60,6 +64,9 @@ import java.io.StringWriter;
public class HandlerUtil { public class HandlerUtil {
private static final Log log = LogFactory.getLog(HandlerUtil.class); private static final Log log = LogFactory.getLog(HandlerUtil.class);
private static LoginCache loginCache = null;
private static boolean isLoginCacheInitialized = false;
private static AuthData authData;
/*** /***
* *
@ -166,9 +173,11 @@ public class HandlerUtil {
/*** /***
* Handle error requests.
* *
* @param resp {@link HttpServletResponse} * @param resp {@link HttpServletResponse}
* Return Error Response. * @param proxyResponse {@link ProxyResponse}
* @throws IOException If error occurred when trying to send the error response.
*/ */
public static void handleError(HttpServletResponse resp, ProxyResponse proxyResponse) throws IOException { public static void handleError(HttpServletResponse resp, ProxyResponse proxyResponse) throws IOException {
Gson gson = new Gson(); Gson gson = new Gson();
@ -188,6 +197,22 @@ public class HandlerUtil {
} }
} }
/**
* Handle error requests with custom error codes.
*
* @param resp {@link HttpServletResponse}
* @param errorCode HTTP error status code
* @throws IOException If error occurred when trying to send the error response.
*/
public static void handleError(HttpServletResponse resp, int errorCode)
throws IOException {
ProxyResponse proxyResponse = new ProxyResponse();
proxyResponse.setCode(errorCode);
proxyResponse.setExecutorResponse(
HandlerConstants.EXECUTOR_EXCEPTION_PREFIX + HandlerUtil.getStatusKey(errorCode));
HandlerUtil.handleError(resp, proxyResponse);
}
/*** /***
* *
* @param resp {@link HttpServletResponse} * @param resp {@link HttpServletResponse}
@ -400,4 +425,136 @@ public class HandlerUtil {
return stringOutput; return stringOutput;
} }
/***
* Search a key from a given json string object.
*
* @param jsonObjectString - json object in string format.
* @param key - the key to be searched.
* @return string value of the key value.
*/
private static String searchFromJsonObjectString(String jsonObjectString, String key) {
JsonParser jsonParser = new JsonParser();
JsonElement jsonElement = jsonParser.parse(jsonObjectString);
JsonObject jsonObject = jsonElement.getAsJsonObject();
return jsonObject.get(key).getAsString();
}
/***
* Initializes the login cache.
*
* @param httpSession - current active HttpSession.
*/
private static void initializeLoginCache(HttpSession httpSession) {
String uiConfig = httpSession.getAttribute(HandlerConstants.UI_CONFIG_KEY).toString();
int capacity = Integer.parseInt(searchFromJsonObjectString(uiConfig, HandlerConstants.LOGIN_CACHE_CAPACITY_KEY));
loginCache = new LoginCache(capacity);
}
/***
* Retrieves login cache and initializes if its not done already.
*
* @param httpSession - current active HttpSession.
*/
public static LoginCache getLoginCache(HttpSession httpSession) {
if (!isLoginCacheInitialized || loginCache == null) {
isLoginCacheInitialized = true;
initializeLoginCache(httpSession);
}
return loginCache;
}
/**
* Retry request again after refreshing the access token
*
* @param req incoming {@link HttpServletRequest}
* @param resp resp {@link HttpServletResponse}
* @param httpRequest subclass of {@link HttpRequestBase} related to the current request.
* @return {@link ProxyResponse} if successful and <code>null</code> if failed.
* @throws IOException If an error occurs when try to retry the request.
*/
public static ProxyResponse retryRequestWithRefreshedToken(HttpServletRequest req, HttpServletResponse resp,
HttpRequestBase httpRequest, String apiEndpoint) throws IOException {
if (refreshToken(req, resp, apiEndpoint)) {
HttpSession session = req.getSession(false);
if (session == null) {
log.error("Unauthorized, You are not logged in. Please log in to the portal");
handleError(resp, HttpStatus.SC_UNAUTHORIZED);
return null;
}
httpRequest.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BEARER + authData.getAccessToken());
ProxyResponse proxyResponse = HandlerUtil.execute(httpRequest);
if (proxyResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) {
log.error("Error occurred while invoking the API after refreshing the token.");
HandlerUtil.handleError(resp, proxyResponse);
return null;
}
return proxyResponse;
}
return null;
}
/***
* This method is responsible to get the refresh token
*
* @param req {@link HttpServletRequest}
* @param resp {@link HttpServletResponse}
* @return If successfully renew tokens, returns TRUE otherwise return FALSE
* @throws IOException If an error occurs while witting error response to client side or invoke token renewal API
*/
private static boolean refreshToken(HttpServletRequest req, HttpServletResponse resp, String gatewayUrl)
throws IOException {
if (log.isDebugEnabled()) {
log.debug("refreshing the token");
}
HttpPost tokenEndpoint = new HttpPost(
gatewayUrl + HandlerConstants.TOKEN_ENDPOINT);
HttpSession session = req.getSession(false);
if (session == null) {
log.error("Couldn't find a session, hence it is required to login and proceed.");
handleError(resp, HttpStatus.SC_UNAUTHORIZED);
return false;
}
authData = (AuthData) session.getAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY);
StringEntity tokenEndpointPayload = new StringEntity(
"grant_type=refresh_token&refresh_token=" + authData.getRefreshToken() + "&scope=PRODUCTION",
ContentType.APPLICATION_FORM_URLENCODED);
tokenEndpoint.setEntity(tokenEndpointPayload);
String encodedClientApp = authData.getEncodedClientApp();
tokenEndpoint.setHeader(HttpHeaders.AUTHORIZATION, HandlerConstants.BASIC +
encodedClientApp);
tokenEndpoint.setHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.toString());
ProxyResponse tokenResultResponse = HandlerUtil.execute(tokenEndpoint);
if (tokenResultResponse.getExecutorResponse().contains(HandlerConstants.EXECUTOR_EXCEPTION_PREFIX)) {
log.error("Error occurred while refreshing access token.");
HandlerUtil.handleError(resp, tokenResultResponse);
return false;
}
JsonParser jsonParser = new JsonParser();
JsonElement jTokenResult = jsonParser.parse(tokenResultResponse.getData());
if (jTokenResult.isJsonObject()) {
JsonObject jTokenResultAsJsonObject = jTokenResult.getAsJsonObject();
AuthData newAuthData = new AuthData();
newAuthData.setAccessToken(jTokenResultAsJsonObject.get("access_token").getAsString());
newAuthData.setRefreshToken(jTokenResultAsJsonObject.get("refresh_token").getAsString());
newAuthData.setScope(jTokenResultAsJsonObject.get("scope").getAsString());
newAuthData.setClientId(authData.getClientId());
newAuthData.setClientSecret(authData.getClientSecret());
newAuthData.setEncodedClientApp(authData.getEncodedClientApp());
newAuthData.setUsername(authData.getUsername());
authData = newAuthData;
session.setAttribute(HandlerConstants.SESSION_AUTH_DATA_KEY, newAuthData);
return true;
}
log.error("Error Occurred in token renewal process.");
handleError(resp, HttpStatus.SC_INTERNAL_SERVER_ERROR);
return false;
}
} }

@ -22,6 +22,8 @@
<EnableSSO>true</EnableSSO> <EnableSSO>true</EnableSSO>
<!-- session time out in seconds --> <!-- session time out in seconds -->
<SessionTimeOut>3600</SessionTimeOut> <SessionTimeOut>3600</SessionTimeOut>
<!-- maximum number of login cache entries -->
<LoginCacheCapacity>10000</LoginCacheCapacity>
<AppRegistration> <AppRegistration>
<Tags> <Tags>
<Tag>application_management</Tag> <Tag>application_management</Tag>

Loading…
Cancel
Save