From 993ea53c6ae1bfbd1c1e95a6132fbe983485de73 Mon Sep 17 00:00:00 2001 From: Alessio Fabiani Date: Tue, 9 Jul 2024 09:53:41 +0200 Subject: [PATCH] [FIXES #2971] MapStore - SSO keycloak kerberos --- .../GeoStoreKeycloakAuthProvider.java | 6 +- .../security/keycloak/KeyCloakFilter.java | 57 ++++++++++++------- .../keycloak/KeyCloakLoginService.java | 35 +++++++----- .../keycloak/KeycloakCookieUtils.java | 14 ++++- 4 files changed, 72 insertions(+), 40 deletions(-) diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/GeoStoreKeycloakAuthProvider.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/GeoStoreKeycloakAuthProvider.java index d2ff040e..c6a5378d 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/GeoStoreKeycloakAuthProvider.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/GeoStoreKeycloakAuthProvider.java @@ -88,8 +88,8 @@ public GeoStoreKeycloakAuthProvider(KeyCloakConfiguration configuration) { } @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication; OidcKeycloakAccount account = token.getAccount(); KeycloakSecurityContext context = account.getKeycloakSecurityContext(); @@ -130,7 +130,7 @@ public Authentication authenticate(Authentication authentication) throws Authent User user = retrieveUser(username, "", grantedAuthoritiesMapper,keycloakGroups); addEveryOne(user.getGroups()); if (user.getRole() == null) { - // no role get the one configured to be default for authenticated users. + // no role gets the one configured to be default for authenticated users. Role defRole = configuration.getAuthenticatedDefaultRole(); user.setRole(defRole); } diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakFilter.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakFilter.java index b3758c75..473a5039 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakFilter.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakFilter.java @@ -27,40 +27,45 @@ */ package it.geosolutions.geostore.services.rest.security.keycloak; +import static it.geosolutions.geostore.services.rest.SessionServiceDelegate.PROVIDER_KEY; +import static it.geosolutions.geostore.services.rest.security.keycloak.KeyCloakLoginService.KEYCLOAK_REDIRECT; +import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.*; +import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.getResponse; + import it.geosolutions.geostore.services.UserService; import it.geosolutions.geostore.services.rest.security.TokenAuthenticationCache; import it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils; +import it.geosolutions.geostore.services.rest.utils.GeoStoreContext; +import java.io.IOException; +import java.util.Date; +import java.util.Objects; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.RequestAuthenticator; import org.keycloak.adapters.spi.AuthOutcome; +import org.keycloak.adapters.spi.HttpFacade; +import org.keycloak.adapters.springsecurity.facade.SimpleHttpFacade; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.filter.GenericFilterBean; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Date; - - -import static it.geosolutions.geostore.services.rest.SessionServiceDelegate.PROVIDER_KEY; -import static it.geosolutions.geostore.services.rest.security.keycloak.KeyCloakLoginService.KEYCLOAK_REDIRECT; -import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.ACCESS_TOKEN_PARAM; -import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.REFRESH_TOKEN_PARAM; /** * Keycloak Authentication Filter. Manage the logic to authenticate a user against a keycloak server. */ +@SuppressWarnings("PMD.UnusedLocalVariable") public class KeyCloakFilter extends GenericFilterBean { @@ -140,7 +145,9 @@ protected Authentication authenticateAndUpdateCache(HttpServletRequest request, } else { entryPoint = new KeycloakAuthenticationEntryPoint(authenticator.getChallenge()); } - RequestContextHolder.getRequestAttributes().setAttribute(KEYCLOAK_REDIRECT,entryPoint,0); + + Objects.requireNonNull(RequestContextHolder.getRequestAttributes()) + .setAttribute(KEYCLOAK_REDIRECT, entryPoint, RequestAttributes.SCOPE_REQUEST); } else { LOGGER.warn("Failed to authentication and to redirect the user."); } @@ -151,13 +158,19 @@ protected Authentication authenticateAndUpdateCache(HttpServletRequest request, * Updates the cache with the new Authentication entry. * @param authentication the new Authentication entry. */ - protected void updateCache(Authentication authentication){ - Object details=authentication.getDetails(); - if (details instanceof KeycloakTokenDetails){ - KeycloakTokenDetails keycloakDetails=(KeycloakTokenDetails) details; - String accessToken=keycloakDetails.getAccessToken(); - if (accessToken!=null){ - cache.putCacheEntry(accessToken,authentication); + protected void updateCache(Authentication authentication) { + Object details = authentication.getDetails(); + if (details instanceof KeycloakTokenDetails) { + KeyCloakHelper helper = GeoStoreContext.bean(KeyCloakHelper.class); + KeycloakTokenDetails keycloakDetails = (KeycloakTokenDetails) details; + String accessToken = keycloakDetails.getAccessToken(); + if (accessToken != null) { + cache.putCacheEntry(accessToken, authentication); + if (helper != null) { + HttpFacade facade = new SimpleHttpFacade(getRequest(), getResponse()); + KeycloakDeployment deployment = helper.getDeployment(facade); + KeycloakCookieUtils.setTokenCookie(deployment, facade, keycloakDetails); + } } } } diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakLoginService.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakLoginService.java index 96b32c06..4570ad6e 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakLoginService.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeyCloakLoginService.java @@ -30,26 +30,26 @@ import it.geosolutions.geostore.services.rest.IdPLoginRest; import it.geosolutions.geostore.services.rest.security.oauth2.Oauth2LoginService; +import java.io.IOException; +import java.util.Objects; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Response; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.ws.rs.core.Response; -import java.io.IOException; - - import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.getAccessToken; import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.getRefreshAccessToken; /** - * Keycloak implementation for a LoginService. - * Since keycloak redirects to the url from which the call to the authorization page was issued - * no internal redirect is really performed here. + * Keycloak implementation for a LoginService. Since keycloak redirects to the url from which the + * call to the authorization page was issued, no internal redirect is really performed here. */ public class KeyCloakLoginService extends Oauth2LoginService { @@ -64,16 +64,25 @@ public KeyCloakLoginService(IdPLoginRest loginRest) { @Override public void doLogin(HttpServletRequest request, HttpServletResponse response, String provider) { - AuthenticationEntryPoint challenge = (AuthenticationEntryPoint) RequestContextHolder.getRequestAttributes() - .getAttribute(KEYCLOAK_REDIRECT, 0); - if (challenge != null) { + KeycloakTokenDetails details = getDetails(); + boolean attempInternalRedirect = (details != null && details.getAccessToken() != null); + + AuthenticationEntryPoint challenge = + (AuthenticationEntryPoint) + Objects.requireNonNull(RequestContextHolder.getRequestAttributes()) + .getAttribute(KEYCLOAK_REDIRECT, RequestAttributes.SCOPE_REQUEST); + if (challenge == null) attempInternalRedirect = true; + else if (!attempInternalRedirect) { try { challenge.commence(request, response, null); + attempInternalRedirect = false; } catch (Exception e) { LOGGER.error("Error while redirecting to Keycloak authorization.", e); throw new RuntimeException(e); } - } else { + } + + if (attempInternalRedirect) { try { response.sendRedirect(configuration(provider).getInternalRedirectUri()); } catch (IOException e) { diff --git a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeycloakCookieUtils.java b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeycloakCookieUtils.java index 57756c6e..14a18c73 100644 --- a/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeycloakCookieUtils.java +++ b/src/modules/rest/impl/src/main/java/it/geosolutions/geostore/services/rest/security/keycloak/KeycloakCookieUtils.java @@ -51,8 +51,18 @@ static void setTokenCookie(KeycloakDeployment deployment, HttpFacade facade, Key .append(refreshToken).toString(); String cookiePath = getCookiePath(deployment, facade); - // forces the expiration of the old keycloak cookie after refresh token. Keycloak doesn't do it for us. - facade.getResponse().setCookie(AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, cookie, cookiePath, null, 0, deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), true); + + // Forces the expiration of the old keycloak cookie after refresh token. Keycloak doesn't do + // it for us. + facade.getResponse() + .setCookie( + AdapterConstants.KEYCLOAK_ADAPTER_STATE_COOKIE, + cookie, + cookiePath, + null, + 0, + deployment.getSslRequired().isRequired(facade.getRequest().getRemoteAddr()), + true); } static String getCookiePath(KeycloakDeployment deployment, HttpFacade facade) {