Modify login cache implementatation with LRU cache

merge-requests/789/head
Vigneshan Seshamany 3 years ago
parent 4cfd56efa0
commit 0eb74d9f60

@ -32,6 +32,7 @@ public class UIConfiguration {
private List<String> scopes;
private boolean isSsoEnable;
private int sessionTimeOut;
private int loginCacheCapacity;
@XmlElement(name = "AppRegistration", required=true)
public AppRegistration getAppRegistration() {
@ -69,4 +70,13 @@ public class UIConfiguration {
public void setSessionTimeOut(int 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.JsonSyntaxException;
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.OAuthAppCacheKey;
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.protocol.HTTP;
import io.entgra.ui.request.interceptor.beans.ProxyResponse;
import org.json.JSONString;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
@ -80,10 +79,9 @@ public class LoginHandler extends HttpServlet {
httpSession.setMaxInactiveInterval(sessionTimeOut);
// Check if OAuth app cache exists. If not create a new application.
LoginCacheManager loginCacheManager = new LoginCacheManager();
loginCacheManager.initializeCacheManager();
LoginCache loginCache = HandlerUtil.getLoginCache(httpSession);
OAuthAppCacheKey oAuthAppCacheKey = new OAuthAppCacheKey(HandlerConstants.PUBLISHER_APPLICATION_NAME, username);
OAuthApp oAuthApp = loginCacheManager.getOAuthAppCache(oAuthAppCacheKey);
OAuthApp oAuthApp = loginCache.getOAuthAppCache(oAuthAppCacheKey);
if (oAuthApp == null) {
HttpPost apiRegEndpoint = new HttpPost(gatewayUrl + HandlerConstants.APP_REG_ENDPOINT);
@ -111,8 +109,6 @@ public class LoginHandler extends HttpServlet {
clientSecret = jClientAppResultAsJsonObject.get("client_secret").getAsString();
encodedClientApp = Base64.getEncoder()
.encodeToString((clientId + HandlerConstants.COLON + clientSecret).getBytes());
oAuthAppCacheKey = new OAuthAppCacheKey(HandlerConstants.PUBLISHER_APPLICATION_NAME, username);
oAuthApp = new OAuthApp(
HandlerConstants.PUBLISHER_APPLICATION_NAME,
username,
@ -120,7 +116,7 @@ public class LoginHandler extends HttpServlet {
clientSecret,
encodedClientApp
);
loginCacheManager.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp);
loginCache.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp);
}
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.JsonParser;
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.OAuthAppCacheKey;
import io.entgra.ui.request.interceptor.util.HandlerConstants;
@ -81,9 +81,9 @@ public class SsoLoginHandler extends HttpServlet {
private JsonObject uiConfigJsonObject;
private HttpSession httpSession;
private LoginCacheManager loginCacheManager;
private LoginCache loginCache;
private OAuthApp oAuthApp;
private OAuthAppCacheKey oAuthAppCacheKey;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
@ -99,13 +99,23 @@ public class SsoLoginHandler extends HttpServlet {
baseContextPath = req.getContextPath();
applicationName = baseContextPath.substring(1, baseContextPath.indexOf("-ui-request-handler"));
// Check if oauth app cache is available
loginCacheManager = new LoginCacheManager();
loginCacheManager.initializeCacheManager();
oAuthApp = loginCacheManager.getOAuthAppCache(
new OAuthAppCacheKey(applicationName, adminUsername)
);
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;
// 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) {
dynamicClientRegistration(req, resp);
}
@ -143,19 +153,6 @@ public class SsoLoginHandler extends HttpServlet {
*/
private void dynamicClientRegistration(HttpServletRequest req, HttpServletResponse resp) {
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 scopes = uiConfigJsonObject.get("scopes").getAsJsonArray();
sessionTimeOut = Integer.parseInt(String.valueOf(uiConfigJsonObject.get("sessionTimeOut")));
@ -191,9 +188,8 @@ public class SsoLoginHandler extends HttpServlet {
}
// cache the oauth app credentials
OAuthAppCacheKey oAuthAppCacheKey = new OAuthAppCacheKey(applicationName, adminUsername);
oAuthApp = new OAuthApp(applicationName, adminUsername, clientId, clientSecret, encodedClientApp);
loginCacheManager.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp);
loginCache.addOAuthAppToCache(oAuthAppCacheKey, oAuthApp);
}
// Get the details of the registered application

@ -18,25 +18,19 @@
package io.entgra.ui.request.interceptor.cache;
import io.entgra.ui.request.interceptor.util.HandlerConstants;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import java.util.LinkedHashMap;
/**
* Contains necessary functions to manage oAuth app cache during login handling
*/
public class LoginCacheManager {
public class LoginCache {
private CacheManager cacheManager = null;
private Cache<OAuthAppCacheKey, OAuthApp> cache = null;
private final LinkedHashMap<OAuthAppCacheKey, OAuthApp> cache;
private final int capacity;
/**
* Initialize the cache manager if it is not already initialized
*/
public void initializeCacheManager() {
cacheManager = Caching.getCacheManagerFactory().getCacheManager(HandlerConstants.LOGIN_CACHE);
public LoginCache(int capacity) {
this.capacity = capacity;
this.cache = new LinkedHashMap<>(capacity);
}
/**
@ -46,7 +40,9 @@ public class LoginCacheManager {
* @param oAuthApp - The value of the cache which contains OAuth app data
*/
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);
}
@ -57,7 +53,13 @@ public class LoginCacheManager {
* @return - Returns OAuthApp object
*/
public OAuthApp getOAuthAppCache(OAuthAppCacheKey oAuthAppCacheKey) {
cache = cacheManager.getCache(HandlerConstants.LOGIN_CACHE);
return cache.get(oAuthAppCacheKey);
OAuthApp oAuthApp = 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 JWT_BEARER_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:jwt-bearer";
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 COLON = ":";

@ -23,13 +23,17 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
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.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.Consts;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
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.conn.ssl.NoopHostnameVerifier;
import org.apache.http.entity.ContentType;
@ -60,6 +64,9 @@ import java.io.StringWriter;
public class HandlerUtil {
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}
* 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 {
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}
@ -400,4 +425,136 @@ public class HandlerUtil {
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>
<!-- session time out in seconds -->
<SessionTimeOut>3600</SessionTimeOut>
<!-- maximum number of login cache entries -->
<LoginCacheCapacity>10000</LoginCacheCapacity>
<AppRegistration>
<Tags>
<Tag>application_management</Tag>

Loading…
Cancel
Save