Moved out synch from method to session object and added checks for response messages from Keycloak. Added also redirection to logout URI in case of revoked or invalid OIDC token.

This commit is contained in:
Mauro Mugnaini 2021-01-11 16:08:53 +01:00
parent b110e21b3f
commit 9a25509add
1 changed files with 192 additions and 99 deletions

View File

@ -10,6 +10,7 @@ import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.apache.catalina.connector.Request;
@ -48,6 +49,13 @@ public class SmartGearsPortalValve extends ValveBase {
private final static String DEFAULT_ROLE = "OrganizationMember";
private final static String LIFERAY_POLLER_CONTEXT = "poller/receive";
private static String LOGOUT_URI = "/c/portal/logout";
private static boolean FORCE_LOGOUT_ON_INVALID_OIDC = true;
private static boolean FORCE_LOGOUT_ON_MISSING_OIDC = true;
private static boolean FORCE_LOGOUT_ON_OIDC_REFRESH_ERROR = true;
private static final int MAX_AUTHORIZATION_RETRY_ATTEMPTS = 4;
private static final int AUTHORIZATION_RETRY_ATTEMPTS_DELAY = 4000;
@Override
public void invoke(Request req, Response resp) throws IOException, ServletException {
SecurityTokenProvider.instance.reset();
@ -75,7 +83,7 @@ public class SmartGearsPortalValve extends ValveBase {
_log.error("Something went wrong in generating token for " + username + " in scope " + scope);
e.printStackTrace();
}
checkUMATicket(request, scope);
checkUMATicket(request, (HttpServletResponse) resp, scope);
//_log.trace("Security token set OK for " + username + " in scope " + scope);
}
@ -84,7 +92,7 @@ public class SmartGearsPortalValve extends ValveBase {
getNext().invoke(req, resp);
}
private synchronized void checkUMATicket(HttpServletRequest request, String scope) {
private void checkUMATicket(HttpServletRequest request, HttpServletResponse response, String scope) {
_log.debug("Getting current user");
User user = getCurrentUser(request);
if (user == null) {
@ -97,107 +105,192 @@ public class SmartGearsPortalValve extends ValveBase {
_log.debug("Session is null, cannot continue");
return;
}
String urlEncodedScope = null;
try {
urlEncodedScope = URLEncoder.encode(scope, "UTF-8");
} catch (UnsupportedEncodingException e) {
_log.error("Cannot URL encode scope", e);
return;
}
_log.debug("URL encoded scope is: {}", urlEncodedScope);
_log.trace("Getting UMA token from session: {}", session);
JWTToken umaToken = JWTTokenUtil.getUMAFromSession(session);
if (umaToken == null) {
_log.debug("UMA token not found in session. Trying to get it from cache proxy");
umaToken = JWTCacheProxy.getInstance().getUMAToken(user, session);
}
if (umaToken != null && !umaToken.isExpired() && umaToken.getAud().contains(urlEncodedScope)) {
if (JWTTokenUtil.getUMAFromSession(session) == null) {
_log.debug("Setting UMA token in session");
synchronized (session) {
String urlEncodedScope = null;
try {
urlEncodedScope = URLEncoder.encode(scope, "UTF-8");
} catch (UnsupportedEncodingException e) {
// Almost impossible
_log.error("Cannot URL encode scope", e);
return;
}
_log.debug("URL encoded scope is: {}", urlEncodedScope);
_log.trace("Getting UMA token from session: {}", session);
JWTToken umaToken = JWTTokenUtil.getUMAFromSession(session);
if (umaToken == null) {
_log.debug("UMA token not found in session. Trying to get it from cache proxy");
umaToken = JWTCacheProxy.getInstance().getUMAToken(user, session);
}
if (umaToken != null && !umaToken.isExpired() && umaToken.getAud().contains(urlEncodedScope)) {
_log.trace("Current UMA token is OK");
if (JWTTokenUtil.getUMAFromSession(session) == null) {
_log.debug("Setting UMA token also in current session");
JWTTokenUtil.putUMAInSession(umaToken, session);
}
} else if (JWTCacheProxy.getInstance().getUMAToken(user, session) != null
&& !JWTCacheProxy.getInstance().getUMAToken(user, session).isExpired()
&& JWTCacheProxy.getInstance().getUMAToken(user, session).getAud().contains(urlEncodedScope)) {
_log.debug("Cache proxy already contains the suitable UMA token. Putting it also in session and using it");
umaToken = JWTCacheProxy.getInstance().getUMAToken(user, session);
JWTTokenUtil.putUMAInSession(umaToken, session);
} else {
if (umaToken != null && umaToken.getAud().contains(urlEncodedScope) && umaToken.isExpired()) {
_log.debug("UMA token is expired, trying to refresh it");
OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration.getConfiguration(request);
try {
umaToken = OpenIdConnectRESTHelper.refreshToken(configuration.getTokenURL(), umaToken);
_log.debug("Setting refreshed UMA token in cache proxy");
JWTCacheProxy.getInstance().setUMAToken(getCurrentUser(request), session, umaToken);
_log.debug("Setting refreshed UMA token in session");
JWTTokenUtil.putUMAInSession(umaToken, session);
} catch (OpenIdConnectRESTHelperException e) {
if (e.hasJSONPayload()) {
if (OpenIdConnectRESTHelper.isInvalidBearerTokenError(e.getResponseString())) {
if (FORCE_LOGOUT_ON_INVALID_OIDC) {
_log.warn("OIDC token is become invalid, forcing redirect to logout URI");
forceLogout(session, response);
} else {
_log.warn("OIDC token is become invalid, cannot continue");
}
return;
} else if (OpenIdConnectRESTHelper.isTokenNotActiveError(e.getResponseString())) {
_log.info("UMA token is no more active, get new one");
}
} else {
_log.error("Refreshing UMA token on server", e);
}
umaToken = null;
_log.info("Removing probably inactive OIDC token from session");
JWTTokenUtil.removeOIDCFromSession(session);
_log.info("Removing all inactive UMA token from session and from cache proxy if present");
JWTTokenUtil.removeUMAFromSession(session);
JWTCacheProxy.getInstance().removeUMAToken(user, session);
}
}
if (umaToken == null || !umaToken.getAud().contains(urlEncodedScope)) {
boolean scopeIsChanged = false;
if (umaToken == null) {
_log.debug("Getting new UMA token for scope {}", urlEncodedScope);
} else if (!umaToken.getAud().contains(urlEncodedScope)) {
scopeIsChanged = true;
_log.info("UMA token has been issued for another scope ({}). Getting new one for scope: {}",
umaToken.getAud(), urlEncodedScope);
}
_log.debug("Getting OIDC token from session");
JWTToken authToken = JWTTokenUtil.getOIDCFromSession(session);
if (authToken == null) {
_log.debug("OIDC token not found in session. Trying to get it from cache proxy");
authToken = JWTCacheProxy.getInstance().getOIDCToken(user, session);
if (authToken == null) {
_log.info("OIDC token is null also in cache proxy");
if (FORCE_LOGOUT_ON_MISSING_OIDC) {
_log.warn("OIDC token is null also in cache proxy, force redirecting to logut URI");
forceLogout(session, response);
return;
} else {
_log.error("OIDC token is null also in cache proxy, cannot continue!");
return;
}
} else {
_log.debug("Setting OIDC token took from cache proxy in session");
JWTTokenUtil.putOIDCInSession(authToken, session);
}
}
OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration.getConfiguration(request);
boolean OK = false;
boolean isNotAuthorized = false;
int authorizationAttempts = 0;
while (!OK) {
try {
if (isNotAuthorized || scopeIsChanged || authToken.isExpired()) {
if (isNotAuthorized) {
_log.info(
"UMA token is not authorized with current OIDC token, "
+ "refreshing it to be sure that new grants are present. "
+ "[attempts: {}]",
authorizationAttempts);
} else if (scopeIsChanged) {
_log.info("Scope is changed, refreshing token to be sure that new grants are present");
} else if (authToken.isExpired()) {
_log.debug("OIDC token is expired, trying to refresh it");
}
try {
authToken = OpenIdConnectRESTHelper.refreshToken(configuration.getTokenURL(),
authToken);
} catch (OpenIdConnectRESTHelperException e) {
if (FORCE_LOGOUT_ON_OIDC_REFRESH_ERROR) {
_log.warn("Error refreshing OIDC token, force redirecting to logut URI");
forceLogout(session, response);
} else {
_log.error("Refreshing OIDC token on server", e);
}
return;
}
_log.debug("Setting refreshed OIDC token in cache proxy and session");
JWTCacheProxy.getInstance().setOIDCToken(user, session, authToken);
JWTTokenUtil.putOIDCInSession(authToken, session);
}
_log.info("Getting UMA token from OIDC endpoint for scope: " + urlEncodedScope);
umaToken = OpenIdConnectRESTHelper.queryUMAToken(configuration.getTokenURL(),
authToken.getAccessTokenAsBearer(), urlEncodedScope, null);
OK = true;
} catch (OpenIdConnectRESTHelperException e) {
if (e.hasJSONPayload()) {
if (OpenIdConnectRESTHelper.isInvalidBearerTokenError(e.getResponseString())) {
if (FORCE_LOGOUT_ON_INVALID_OIDC) {
_log.warn("OIDC token is become invalid, forcing redirect to logout URI");
forceLogout(session, response);
} else {
_log.error("OIDC token is become invalid, cannot continue");
}
return;
} else if (OpenIdConnectRESTHelper
.isAccessDeniedNotAuthorizedError(e.getResponseString())) {
_log.info("UMA token is" + (isNotAuthorized ? " still" : "")
+ " not authorized with actual OIDC token");
isNotAuthorized = true;
authorizationAttempts += 1;
if (authorizationAttempts <= MAX_AUTHORIZATION_RETRY_ATTEMPTS) {
_log.debug("Sleeping " + AUTHORIZATION_RETRY_ATTEMPTS_DELAY
+ " ms and refreshing the OIDC");
try {
Thread.sleep(AUTHORIZATION_RETRY_ATTEMPTS_DELAY);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
} else {
_log.warn("OIDC token refresh attempts exhausted");
return;
}
}
} else {
_log.error("Getting UMA token from server", e);
return;
}
}
}
}
_log.debug("Setting UMA token in cache proxy and in session");
JWTCacheProxy.getInstance().setUMAToken(user, session, umaToken);
JWTTokenUtil.putUMAInSession(umaToken, session);
}
} else {
if (umaToken != null && umaToken.getAud().contains(urlEncodedScope) && umaToken.isExpired()) {
_log.debug("UMA token is expired, trying to refresh it");
OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration.getConfiguration(request);
try {
umaToken = OpenIdConnectRESTHelper.refreshToken(configuration.getTokenURL(), umaToken);
_log.debug("Setting refreshed UMA token in cache proxy");
JWTCacheProxy.getInstance().setUMAToken(getCurrentUser(request), session, umaToken);
_log.debug("Setting refreshed UMA token in session");
JWTTokenUtil.putUMAInSession(umaToken, session);
} catch (OpenIdConnectRESTHelperException e) {
if (e.hasJSONPayload() && OpenIdConnectRESTHelper.isTokenNotActiveError(e.getResponseString())) {
_log.info("UMA token is no more active, get new one");
} else {
_log.warn("Refreshing UMA token on server", e);
}
umaToken = null;
_log.info("Removing probably inactive OIDC token from session");
JWTTokenUtil.removeOIDCFromSession(session);
_log.info("Removing all inactive UMA token from session and from cache proxy if present");
JWTTokenUtil.removeUMAFromSession(session);
JWTCacheProxy.getInstance().removeUMAToken(user, session);
}
}
if (umaToken == null || !umaToken.getAud().contains(urlEncodedScope)) {
boolean scopeIsChanged = false;
if (umaToken == null) {
_log.debug("Getting new UMA token for scope {}", urlEncodedScope);
} else if (umaToken.getAud().contains(urlEncodedScope)) {
scopeIsChanged = true;
_log.info("UMA token has been issued for another scope ({}). Getting new one for scope: {}",
umaToken.getAud(), urlEncodedScope);
}
_log.debug("Getting OIDC token from session");
JWTToken authToken = JWTTokenUtil.getOIDCFromSession(session);
if (authToken == null) {
_log.debug("OIDC token not found in session. Trying to get it from cache proxy");
authToken = JWTCacheProxy.getInstance().getOIDCToken(user, session);
if (authToken == null) {
_log.warn("OIDC token is null also in cache proxy, cannot continue!");
return;
} else {
_log.debug("Setting OIDC token took from cache proxy in session");
JWTTokenUtil.putOIDCInSession(authToken, session);
}
}
OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration.getConfiguration(request);
try {
if (scopeIsChanged || authToken.isExpired()) {
if (scopeIsChanged) {
_log.info("Scope is changed, refreshing token to be sure that new grants are present");
} else {
_log.debug("OIDC token is expired, refreshing it");
}
try {
authToken = OpenIdConnectRESTHelper.refreshToken(configuration.getTokenURL(), authToken);
} catch (OpenIdConnectRESTHelperException e) {
_log.error("Refreshing OIDC token on server", e);
// TODO check if a session not active token and consider if force redirect to /c/portal/logout
return;
}
_log.debug("Setting refreshed OIDC token in cache proxy and in session");
JWTCacheProxy.getInstance().setOIDCToken(user, session, authToken);
JWTTokenUtil.putOIDCInSession(authToken, session);
}
_log.info("Getting UMA token from OIDC endpoint for scope: " + urlEncodedScope);
umaToken = OpenIdConnectRESTHelper.queryUMAToken(configuration.getTokenURL(),
authToken.getAccessTokenAsBearer(), urlEncodedScope, null);
} catch (OpenIdConnectRESTHelperException e) {
_log.error("Getting UMA token from server", e);
return;
}
}
_log.debug("Setting UMA token in cache proxy and in session");
JWTCacheProxy.getInstance().setUMAToken(user, session, umaToken);
JWTTokenUtil.putUMAInSession(umaToken, session);
_log.trace("Current UMA token audience is: {}", umaToken.getAud());
_log.debug("Setting UMA token in UMA JWT provider");
UmaJWTProvider.instance.set(umaToken.getRaw());
}
}
_log.trace("Current UMA token audience is: {}", umaToken.getAud());
_log.debug("Setting UMA token in UMA JWT provider");
UmaJWTProvider.instance.set(umaToken.getRaw());
protected void forceLogout(HttpSession session, HttpServletResponse response) {
try {
response.sendRedirect(LOGOUT_URI);
} catch (IOException e) {
_log.error("Cannot redirect to logout URI: " + LOGOUT_URI, e);
}
}
/**