From 96da141c39df1cf7b047e82518f24693611136c6 Mon Sep 17 00:00:00 2001 From: phil Date: Mon, 25 Nov 2024 16:03:48 +0100 Subject: [PATCH] adding a generic OIDC logout URL --- .../security/CustomLogoutSuccessHandler.java | 47 +++++++++++++------ .../SPDF/model/ApplicationProperties.java | 1 + src/main/resources/settings.yml.template | 1 + 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java b/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java index 8d5aa76d015..20290275383 100644 --- a/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java +++ b/src/main/java/stirling/software/SPDF/config/security/CustomLogoutSuccessHandler.java @@ -5,11 +5,15 @@ import java.security.interfaces.RSAPrivateKey; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringSubstitutor; import org.springframework.core.io.Resource; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; @@ -150,16 +154,19 @@ private void getRedirect_saml2( private void getRedirect_oauth2( HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); String param = "logout=true"; String registrationId = null; - String issuer = null; - String clientId = null; - OAUTH2 oauth = applicationProperties.getSecurity().getOauth2(); + String issuer = oauth.getIssuer(); + String clientId = oauth.getClientId(); + String idToken = null; if (authentication instanceof OAuth2AuthenticationToken) { OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication; registrationId = oauthToken.getAuthorizedClientRegistrationId(); - + if (oauthToken.getPrincipal() instanceof OidcUser oidcUser) { + idToken = oidcUser.getIdToken().getTokenValue(); + } try { // Get OAuth2 provider details from configuration Provider provider = oauth.getClient().get(registrationId); @@ -191,8 +198,7 @@ private void getRedirect_oauth2( param = "error=badcredentials"; } - String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param; - + String defaultRedirectUrl = UrlUtils.getOrigin(request) + "/login?" + param; // Redirect based on OAuth2 provider switch (registrationId.toLowerCase()) { case "keycloak": @@ -203,7 +209,7 @@ private void getRedirect_oauth2( + "?client_id=" + clientId + "&post_logout_redirect_uri=" - + response.encodeRedirectURL(redirect_url); + + response.encodeRedirectURL(defaultRedirectUrl); log.info("Redirecting to Keycloak logout URL: " + logoutUrl); response.sendRedirect(logoutUrl); break; @@ -218,15 +224,28 @@ private void getRedirect_oauth2( // String googleLogoutUrl = // "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue=" // + response.encodeRedirectURL(redirect_url); - log.info("Google does not have a specific logout URL"); - // log.info("Redirecting to Google logout URL: " + googleLogoutUrl); - // response.sendRedirect(googleLogoutUrl); - // break; - default: - String defaultRedirectUrl = request.getContextPath() + "/login?" + param; - log.info("Redirecting to default logout URL: " + defaultRedirectUrl); + log.info( + "Google does not have a specific logout URL, redirecting to default logout URL: " + + defaultRedirectUrl); response.sendRedirect(defaultRedirectUrl); break; + default: + String customRedirectUrl = defaultRedirectUrl; + if (StringUtils.isNotBlank(oauth.getLogoutUrl())) { + customRedirectUrl = + issuer + + StringSubstitutor.replace( + oauth.getLogoutUrl(), + Map.of( + "clientId", StringUtils.defaultString(clientId), + "tokenId", StringUtils.defaultString(idToken), + "redirectUrl", defaultRedirectUrl), + "{", + "}"); + } + log.info("Redirecting to logout URL: " + customRedirectUrl); + response.sendRedirect(customRedirectUrl); + break; } } diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 83df30ae04d..4cf1baadd3f 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -183,6 +183,7 @@ public static class OAUTH2 { @ToString.Exclude private String clientSecret; private Boolean autoCreateUser = false; private Boolean blockRegistration = false; + private String logoutUrl; private String useAsUsername; private Collection scopes = new ArrayList<>(); private String provider; diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index d9971d0c868..06770220ddb 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -47,6 +47,7 @@ security: useAsUsername: email # default is 'email'; custom fields can be used as the username scopes: openid, profile, email # specify the scopes for which the application will request permissions provider: google # set this to your OAuth provider's name, e.g., 'google' or 'keycloak' + logoutUrl: '' # logout URL to call at he issuer, may be used with 3 variables, for instance : '/connect/logout?id_token_hint={tokenId}&client_id={clientId}&post_logout_redirect_uri={redirectUrl}' saml2: enabled: false # currently in alpha, not recommended for use yet, enableAlphaFunctionality must be set to true autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users