From ca42baf97970a79ca7ce428e219359fcc9991d80 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 (#356) --- .../GeoStoreKeycloakAuthProvider.java | 153 ++++++++-------- .../security/keycloak/KeyCloakFilter.java | 171 ++++++++++-------- .../keycloak/KeyCloakLoginService.java | 51 +++--- .../keycloak/KeycloakCookieUtils.java | 36 ++-- 4 files changed, 223 insertions(+), 188 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..90aae1d9 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 @@ -28,6 +28,8 @@ package it.geosolutions.geostore.services.rest.security.keycloak; +import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.*; + import it.geosolutions.geostore.core.model.User; import it.geosolutions.geostore.core.model.UserGroup; import it.geosolutions.geostore.core.model.enums.GroupReservedNames; @@ -37,8 +39,9 @@ import it.geosolutions.geostore.services.UserService; import it.geosolutions.geostore.services.exception.BadRequestServiceEx; import it.geosolutions.geostore.services.exception.NotFoundServiceEx; +import java.util.*; +import javax.servlet.http.HttpServletRequest; import org.apache.logging.log4j.Level; - import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.keycloak.KeycloakSecurityContext; @@ -55,41 +58,24 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; -import javax.servlet.http.HttpServletRequest; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -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; -import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.getRequest; - /** - * GeoStore custom Authentication provider. It is used to map a Keycloak Authentication to a GeoStore Authentication where - * the principal is of type {@link User}. + * GeoStore custom Authentication provider. It is used to map a Keycloak Authentication to a + * GeoStore Authentication where the principal is of type {@link User}. */ public class GeoStoreKeycloakAuthProvider implements AuthenticationProvider { - private final static Logger LOGGER = LogManager.getLogger(GeoStoreKeycloakAuthProvider.class); - - - @Autowired - private UserService userService; - - @Autowired - private UserGroupService groupService; - - private KeyCloakConfiguration configuration; + private static final Logger LOGGER = LogManager.getLogger(GeoStoreKeycloakAuthProvider.class); + private final KeyCloakConfiguration configuration; + @Autowired private UserService userService; + @Autowired private UserGroupService groupService; public GeoStoreKeycloakAuthProvider(KeyCloakConfiguration configuration) { this.configuration = 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(); @@ -98,7 +84,8 @@ public Authentication authenticate(Authentication authentication) throws Authent String refreshToken = null; Long expiration = null; HttpServletRequest request = getRequest(); - // set tokens as request attributes so that can made available in a cookie for the frontend on the callback url. + // set tokens as request attributes so that can made available in a cookie for the frontend + // on the callback url. if (accessToken != null) { expiration = accessToken.getExp(); if (request != null) request.setAttribute(ACCESS_TOKEN_PARAM, accessToken); @@ -108,40 +95,51 @@ public Authentication authenticate(Authentication authentication) throws Authent if (request != null) request.setAttribute(REFRESH_TOKEN_PARAM, refreshToken); } - List grantedAuthorities = new ArrayList<>(); - GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper = new GeoStoreKeycloakAuthoritiesMapper(configuration.getRoleMappings(), configuration.getGroupMappings(), configuration.isDropUnmapped()); + GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper = + new GeoStoreKeycloakAuthoritiesMapper( + configuration.getRoleMappings(), + configuration.getGroupMappings(), + configuration.isDropUnmapped()); for (String role : token.getAccount().getRoles()) { grantedAuthorities.add(new KeycloakRole(role)); } // maps authorities to GeoStore Role and UserGroup - Collection mapped = mapAuthorities(grantedAuthoritiesMapper, grantedAuthorities); + Collection mapped = + mapAuthorities(grantedAuthoritiesMapper, grantedAuthorities); - KeycloakTokenDetails details = new KeycloakTokenDetails(accessTokenStr, refreshToken, expiration); + KeycloakTokenDetails details = + new KeycloakTokenDetails(accessTokenStr, refreshToken, expiration); details.setIdToken(context.getIdTokenString()); String username = getUsername(authentication); - Set keycloakGroups = grantedAuthoritiesMapper != null ? grantedAuthoritiesMapper.getGroups() : new HashSet<>(); + Set keycloakGroups = + grantedAuthoritiesMapper != null + ? grantedAuthoritiesMapper.getGroups() + : new HashSet<>(); - // if the auto creation of user is set to true from keycloak properties we add the groups as well. + // if the auto creation of user is set to true from keycloak properties we add the groups as + // well. if (configuration.isAutoCreateUser()) keycloakGroups = importGroups(keycloakGroups, grantedAuthorities); - User user = retrieveUser(username, "", grantedAuthoritiesMapper,keycloakGroups); + 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); } if (user.getGroups() == null) user.setGroups(new HashSet<>()); - PreAuthenticatedAuthenticationToken result = new PreAuthenticatedAuthenticationToken(user, "", mapped); + PreAuthenticatedAuthenticationToken result = + new PreAuthenticatedAuthenticationToken(user, "", mapped); result.setDetails(details); return result; } - private Collection mapAuthorities(GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper, - Collection authorities) { + private Collection mapAuthorities( + GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper, + Collection authorities) { return grantedAuthoritiesMapper != null ? grantedAuthoritiesMapper.mapAuthorities(authorities) : authorities; @@ -153,14 +151,18 @@ public boolean supports(Class aClass) { } /** - * Retrieve the user from db or create a new instance. If {@link KeyCloakConfiguration#isAutoCreateUser()} returns - * true, will insert the user in the db. + * Retrieve the user from db or create a new instance. If {@link + * KeyCloakConfiguration#isAutoCreateUser()} returns true, will insert the user in the db. * * @param userName * @param credentials * @return */ - protected User retrieveUser(String userName, String credentials, GeoStoreKeycloakAuthoritiesMapper mapper,Setgroups) { + protected User retrieveUser( + String userName, + String credentials, + GeoStoreKeycloakAuthoritiesMapper mapper, + Set groups) { User user = null; if (userService != null) { try { @@ -178,7 +180,7 @@ protected User retrieveUser(String userName, String credentials, GeoStoreKeycloa user.setEnabled(true); Role role = mappedRole(mapper); user.setRole(role); - if (groups==null) groups=new HashSet<>(); + if (groups == null) groups = new HashSet<>(); user.setGroups(groups); // user not found in db, if configured to insert will insert it. if (userService != null && configuration.isAutoCreateUser()) { @@ -194,52 +196,50 @@ protected User retrieveUser(String userName, String credentials, GeoStoreKeycloa } else { Role role = mappedRole(mapper); // might need to update the role / groups if on keycloak side roles changed. - if (isUpdateUser(user,groups,role)) { - updateRoleAndGroups(role,groups,user); + if (isUpdateUser(user, groups, role)) { + updateRoleAndGroups(role, groups, user); } } return user; } // update user groups adding the one not already present and added on keycloak side - private User updateRoleAndGroups(Role role,Set groups, User user){ - user.setRole(role); - try { - for (UserGroup g:user.getGroups()){ - if (!groups.stream().anyMatch(group->group.getGroupName().equals(g.getGroupName()))) { - UserGroup newGroup = new UserGroup(); - newGroup.setGroupName(g.getGroupName()); - newGroup.setId(g.getId()); - groups.add(g); - } + private User updateRoleAndGroups(Role role, Set groups, User user) { + user.setRole(role); + try { + for (UserGroup g : user.getGroups()) { + if (!groups.stream() + .anyMatch(group -> group.getGroupName().equals(g.getGroupName()))) { + UserGroup newGroup = new UserGroup(); + newGroup.setGroupName(g.getGroupName()); + newGroup.setId(g.getId()); + groups.add(g); } - user.setGroups(groups); - userService.update(new User(user)); - user = userService.get(user.getName()); - } catch (NotFoundServiceEx | BadRequestServiceEx ex) { - LOGGER.error("Error while updating user role...", ex); } + user.setGroups(groups); + userService.update(new User(user)); + user = userService.get(user.getName()); + } catch (NotFoundServiceEx | BadRequestServiceEx ex) { + LOGGER.error("Error while updating user role...", ex); + } return user; } // we only update if new roles were added on keycloak or the role changed - private boolean isUpdateUser(User user, Set groups, Role mappedRole){ - Set incoming=new HashSet<>(groups); + private boolean isUpdateUser(User user, Set groups, Role mappedRole) { + Set incoming = new HashSet<>(groups); incoming.removeAll(user.getGroups()); - if (!incoming.stream().allMatch(g->g.getGroupName().equals(GroupReservedNames.EVERYONE.groupName()))) + if (!incoming.stream() + .allMatch(g -> g.getGroupName().equals(GroupReservedNames.EVERYONE.groupName()))) return true; - if (configuration.isAutoCreateUser() && (user.getRole() == null || !user.getRole().equals(mappedRole))) - return true; - - return false; - + return configuration.isAutoCreateUser() + && (user.getRole() == null || !user.getRole().equals(mappedRole)); } private Role mappedRole(GeoStoreKeycloakAuthoritiesMapper mapper) { Role role = null; - if (mapper != null && mapper.getRole() != null) - role = mapper.getRole(); + if (mapper != null && mapper.getRole() != null) role = mapper.getRole(); if (role == null) role = configuration.getAuthenticatedDefaultRole(); if (role == null) role = Role.USER; return role; @@ -247,7 +247,8 @@ private Role mappedRole(GeoStoreKeycloakAuthoritiesMapper mapper) { private String getUsername(Authentication authentication) { String username = null; - if (authentication != null && authentication.getDetails() instanceof SimpleKeycloakAccount) { + if (authentication != null + && authentication.getDetails() instanceof SimpleKeycloakAccount) { SimpleKeycloakAccount account = (SimpleKeycloakAccount) authentication.getDetails(); AccessToken token = account.getKeycloakSecurityContext().getToken(); if (token != null) username = token.getPreferredUsername(); @@ -256,7 +257,8 @@ private String getUsername(Authentication authentication) { return username; } - private Set importGroups(Set mappedGroups, Collection authorities) { + private Set importGroups( + Set mappedGroups, Collection authorities) { Set returnSet = new HashSet<>(mappedGroups.size()); try { if (mappedGroups == null || mappedGroups.isEmpty()) { @@ -276,8 +278,7 @@ private Set importGroups(Set mappedGroups, Collection groups){ - String everyone=GroupReservedNames.EVERYONE.groupName(); - if (!groups.stream().anyMatch(g->g.getGroupName().equals(everyone))) { + private void addEveryOne(Set groups) { + String everyone = GroupReservedNames.EVERYONE.groupName(); + if (!groups.stream().anyMatch(g -> g.getGroupName().equals(everyone))) { UserGroup everyoneGroup = new UserGroup(); everyoneGroup.setEnabled(true); everyoneGroup.setId(-1L); 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..d89fd354 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,120 +27,131 @@ */ 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. + * Keycloak Authentication Filter. Manage the logic to authenticate a user against a keycloak + * server. */ +@SuppressWarnings("PMD.UnusedLocalVariable") public class KeyCloakFilter extends GenericFilterBean { - + private static final Logger LOGGER = LogManager.getLogger(KeyCloakFilter.class); // used to map keycloak roles to spring-security roles private final GeoStoreKeycloakAuthProvider authenticationProvider; // creates token stores capable of generating spring-security tokens from keycloak auth // the context of the keycloak environment (realm, URL, client-secrets etc.) - private KeyCloakHelper helper; - - private KeyCloakConfiguration configuration; - - @Autowired - protected UserService userService; - - private TokenAuthenticationCache cache; - - private final static Logger LOGGER = LogManager.getLogger(KeyCloakFilter.class); - + private final KeyCloakHelper helper; + private final KeyCloakConfiguration configuration; + private final TokenAuthenticationCache cache; + @Autowired protected UserService userService; /** * @param helper a {@link KeyCloakHelper} instance. * @param cache an instance of {@link TokenAuthenticationCache} to cache authentication objects. * @param configuration the {@link KeyCloakConfiguration} for this geostore instance. - * @param authenticationProvider the authentication provider to map the Keycloak Authentication to the GeoStore one. + * @param authenticationProvider the authentication provider to map the Keycloak Authentication + * to the GeoStore one. */ - public KeyCloakFilter (KeyCloakHelper helper, TokenAuthenticationCache cache, KeyCloakConfiguration configuration, GeoStoreKeycloakAuthProvider authenticationProvider){ - this.helper=helper; + public KeyCloakFilter( + KeyCloakHelper helper, + TokenAuthenticationCache cache, + KeyCloakConfiguration configuration, + GeoStoreKeycloakAuthProvider authenticationProvider) { + this.helper = helper; this.authenticationProvider = authenticationProvider; - this.cache=cache; - this.configuration=configuration; + this.cache = cache; + this.configuration = configuration; } - @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (enabledAndValid() && SecurityContextHolder.getContext().getAuthentication()==null) { - Authentication authentication = authenticate((HttpServletRequest) request, (HttpServletResponse) response); - if (authentication != null){ + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (enabledAndValid() && SecurityContextHolder.getContext().getAuthentication() == null) { + Authentication authentication = + authenticate((HttpServletRequest) request, (HttpServletResponse) response); + if (authentication != null) { SecurityContextHolder.getContext().setAuthentication(authentication); - if (authentication.getDetails() instanceof KeycloakTokenDetails){ - KeycloakTokenDetails details=(KeycloakTokenDetails) authentication.getDetails(); - if (details.getAccessToken()!=null) RequestContextHolder.getRequestAttributes().setAttribute(ACCESS_TOKEN_PARAM,details.getAccessToken(),0); - if (details.getRefreshToken()!=null) RequestContextHolder.getRequestAttributes().setAttribute(REFRESH_TOKEN_PARAM,details.getRefreshToken(),0); + if (authentication.getDetails() instanceof KeycloakTokenDetails) { + KeycloakTokenDetails details = + (KeycloakTokenDetails) authentication.getDetails(); + if (details.getAccessToken() != null) + RequestContextHolder.getRequestAttributes() + .setAttribute(ACCESS_TOKEN_PARAM, details.getAccessToken(), 0); + if (details.getRefreshToken() != null) + RequestContextHolder.getRequestAttributes() + .setAttribute(REFRESH_TOKEN_PARAM, details.getRefreshToken(), 0); } } - RequestContextHolder.getRequestAttributes().setAttribute(PROVIDER_KEY,"keycloak",0); + RequestContextHolder.getRequestAttributes().setAttribute(PROVIDER_KEY, "keycloak", 0); } - chain.doFilter(request,response); + chain.doFilter(request, response); } - private boolean enabledAndValid(){ - return configuration.isEnabled() && configuration.getJsonConfig()!=null; + private boolean enabledAndValid() { + return configuration.isEnabled() && configuration.getJsonConfig() != null; } /** * Perform the authentication and updates the cache. + * * @param request the request. * @param response the response. * @return the authentication object. Can be null if the user is not authenticated. */ - protected Authentication authenticateAndUpdateCache(HttpServletRequest request, HttpServletResponse response) { + protected Authentication authenticateAndUpdateCache( + HttpServletRequest request, HttpServletResponse response) { // do some setup and create the authenticator - KeycloakDeployment deployment=helper.getDeployment(request,response); - RequestAuthenticator authenticator = helper.getAuthenticator(request,response,deployment); + KeycloakDeployment deployment = helper.getDeployment(request, response); + RequestAuthenticator authenticator = helper.getAuthenticator(request, response, deployment); // perform the authentication operation AuthOutcome result = authenticator.authenticate(); - Authentication auth=null; + Authentication auth = null; if (result.equals(AuthOutcome.AUTHENTICATED)) { - auth = SecurityContextHolder.getContext().getAuthentication(); - auth=authenticationProvider.authenticate(auth); - updateCache(auth); - } else if (result.equals(AuthOutcome.NOT_ATTEMPTED)){ + auth = SecurityContextHolder.getContext().getAuthentication(); + auth = authenticationProvider.authenticate(auth); + updateCache(auth); + } else if (result.equals(AuthOutcome.NOT_ATTEMPTED)) { AuthenticationEntryPoint entryPoint; if (deployment.isBearerOnly()) { // if bearer-only, then missing auth means you are forbidden - entryPoint=new KeycloakAuthenticationEntryPoint(null); + entryPoint = new KeycloakAuthenticationEntryPoint(null); } 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."); } @@ -149,46 +160,56 @@ 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); + } } } } /** - * Performs the authentication. The method will check the cache before calling keycloak. - * If the token is expired, a new authentication is anyway issued and the cache updated. + * Performs the authentication. The method will check the cache before calling keycloak. If the + * token is expired, a new authentication is anyway issued and the cache updated. + * * @param request the request. * @param response the response. * @return the authentication. */ - protected Authentication authenticate(HttpServletRequest request,HttpServletResponse response){ - Authentication authentication=null; + protected Authentication authenticate( + HttpServletRequest request, HttpServletResponse response) { + Authentication authentication = null; String token = OAuth2Utils.tokenFromParamsOrBearer(ACCESS_TOKEN_PARAM, request); if (token != null) { authentication = cache.get(token); - if (authentication!=null && authentication.getDetails() instanceof KeycloakTokenDetails){ - KeycloakTokenDetails details=(KeycloakTokenDetails) authentication.getDetails(); - if (details.getExpiration().before(new Date())){ - LOGGER.warn("Token has expired and the refresh token endpoint has not been called. The request will not be authorized by the keycloak filter"); + if (authentication != null + && authentication.getDetails() instanceof KeycloakTokenDetails) { + KeycloakTokenDetails details = (KeycloakTokenDetails) authentication.getDetails(); + if (details.getExpiration().before(new Date())) { + LOGGER.warn( + "Token has expired and the refresh token endpoint has not been called. The request will not be authorized by the keycloak filter"); cache.removeEntry(details.getAccessToken()); - authentication=null; + authentication = null; } } if (authentication == null) { authentication = authenticateAndUpdateCache(request, response); } } else { - authentication=authenticateAndUpdateCache(request,response); + authentication = authenticateAndUpdateCache(request, response); } return authentication; } - } 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..77f9d976 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 @@ -27,53 +27,59 @@ */ package it.geosolutions.geostore.services.rest.security.keycloak; +import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.getAccessToken; +import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.getRefreshAccessToken; + 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 { - private final static Logger LOGGER = LogManager.getLogger(KeyCloakLoginService.class); + private static final Logger LOGGER = LogManager.getLogger(KeyCloakLoginService.class); static String KEYCLOAK_REDIRECT = "KEYCLOAK_REDIRECT"; - public KeyCloakLoginService(IdPLoginRest loginRest) { loginRest.registerService("keycloak", this); } @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) { @@ -83,7 +89,8 @@ public void doLogin(HttpServletRequest request, HttpServletResponse response, St } @Override - public Response doInternalRedirect(HttpServletRequest request, HttpServletResponse response, String provider) { + public Response doInternalRedirect( + HttpServletRequest request, HttpServletResponse response, String provider) { String token; String refreshToken; KeycloakTokenDetails details = getDetails(); @@ -91,8 +98,8 @@ public Response doInternalRedirect(HttpServletRequest request, HttpServletRespon token = details.getAccessToken(); refreshToken = details.getRefreshToken(); } else { - token=getAccessToken(); - refreshToken=getRefreshAccessToken(); + token = getAccessToken(); + refreshToken = getRefreshAccessToken(); } return buildCallbackResponse(token, refreshToken, provider); } 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..9eae50f2 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 @@ -27,36 +27,42 @@ */ package it.geosolutions.geostore.services.rest.security.keycloak; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import org.keycloak.adapters.KeycloakDeployment; import org.keycloak.adapters.spi.HttpFacade; import org.keycloak.common.util.KeycloakUriBuilder; import org.keycloak.constants.AdapterConstants; - -/** - * Utility class that provides method to update the Keycloak Adapter cookie. - */ +/** Utility class that provides method to update the Keycloak Adapter cookie. */ class KeycloakCookieUtils { - - private static final String SEPARATOR="___"; - static void setTokenCookie(KeycloakDeployment deployment, HttpFacade facade, KeycloakTokenDetails tokenDetails) { + private static final String SEPARATOR = "___"; + + static void setTokenCookie( + KeycloakDeployment deployment, HttpFacade facade, KeycloakTokenDetails tokenDetails) { String accessToken = tokenDetails.getAccessToken(); String idToken = tokenDetails.getIdToken(); String refreshToken = tokenDetails.getRefreshToken(); - String cookie = new StringBuilder(accessToken).append(SEPARATOR) - .append(idToken).append(SEPARATOR) - .append(refreshToken).toString(); + String cookie = accessToken + SEPARATOR + idToken + SEPARATOR + refreshToken; 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) { - String path = deployment.getAdapterStateCookiePath() == null ? "" : deployment.getAdapterStateCookiePath().trim(); + String path = + deployment.getAdapterStateCookiePath() == null + ? "" + : deployment.getAdapterStateCookiePath().trim(); if (path.startsWith("/")) { return path; }