From 004d21346c9f19619a8db9b5a40f4836408bbb67 Mon Sep 17 00:00:00 2001 From: ch4mpy Date: Tue, 5 Nov 2024 17:18:15 -1000 Subject: [PATCH] Fix oauth2Login auto-config in servlets --- .../SpringAddonsLogoutSuccessHandler.java | 92 ++-- ...onsOAuth2AuthorizationRequestResolver.java | 356 +++++++------ .../SpringAddonsOidcClientWithLoginBeans.java | 494 +++++++++--------- 3 files changed, 505 insertions(+), 437 deletions(-) diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java index d48c76f6d..9164a9bc8 100644 --- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java +++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsLogoutSuccessHandler.java @@ -3,20 +3,18 @@ import java.io.IOException; import java.net.URI; import java.util.Optional; - import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler; import org.springframework.util.StringUtils; - import com.c4_soft.springaddons.security.oidc.starter.LogoutRequestUriBuilder; import com.c4_soft.springaddons.security.oidc.starter.SpringAddonsOAuth2LogoutRequestUriBuilder; import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties; import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcProperties; - import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -24,21 +22,24 @@ /** *

- * Provide with RP-Initiated Logout for authorization-servers fully compliant with - * OIDC standard as well as those "almost" implementing the spec. It is (auto)configured with {@link SpringAddonsOidcClientProperties}. + * Provide with RP-Initiated + * Logout for authorization-servers fully compliant with OIDC standard as well as those "almost" + * implementing the spec. It is (auto)configured with {@link SpringAddonsOidcClientProperties}. *

*

- * This implementation is not multi-tenant ready. It will terminate the user session on this application as well as on a single authorization-server (the - * one which emitted the access-token with which the logout request is made). + * This implementation is not multi-tenant ready. It will terminate the user session on this + * application as well as on a single authorization-server (the one which emitted the access-token + * with which the logout request is made). *

*

- * This bean is auto-configured by {@link SpringAddonsOidcClientWithLoginBeans} as {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type + * This bean is auto-configured by {@link SpringAddonsOidcClientWithLoginBeans} as + * {@link ConditionalOnMissingBean @ConditionalOnMissingBean} of type * {@link LogoutSuccessHandler}. Usage: *

* *
  * SecurityFilterChain uiFilterChain(HttpSecurity http, LogoutSuccessHandler logoutSuccessHandler) {
- *     http.logout().logoutSuccessHandler(logoutSuccessHandler);
+ *   http.logout().logoutSuccessHandler(logoutSuccessHandler);
  * }
  * 
* @@ -48,39 +49,52 @@ */ @EqualsAndHashCode(callSuper = true) public class SpringAddonsLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler { - private final LogoutRequestUriBuilder uriBuilder; - private final ClientRegistrationRepository clientRegistrationRepository; - private final SpringAddonsOauth2RedirectStrategy redirectStrategy; - private final String defaultPostLogoutUri; + private final LogoutRequestUriBuilder uriBuilder; + private final ClientRegistrationRepository clientRegistrationRepository; + private final SpringAddonsOauth2RedirectStrategy redirectStrategy; + private final String defaultPostLogoutUri; - public SpringAddonsLogoutSuccessHandler( - LogoutRequestUriBuilder uriBuilder, - ClientRegistrationRepository clientRegistrationRepository, - SpringAddonsOidcProperties addonsProperties) { - this.defaultPostLogoutUri = Optional.ofNullable(addonsProperties.getClient().getPostLogoutRedirectUri()).map(URI::toString).orElse(null); - this.uriBuilder = uriBuilder; - this.clientRegistrationRepository = clientRegistrationRepository; - this.redirectStrategy = new SpringAddonsOauth2RedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getRpInitiatedLogout()); - } + public SpringAddonsLogoutSuccessHandler(LogoutRequestUriBuilder uriBuilder, + ClientRegistrationRepository clientRegistrationRepository, + SpringAddonsOidcProperties addonsProperties) { + this.defaultPostLogoutUri = + Optional.ofNullable(addonsProperties.getClient().getPostLogoutRedirectUri()) + .map(URI::toString).orElse(null); + this.uriBuilder = uriBuilder; + this.clientRegistrationRepository = clientRegistrationRepository; + this.redirectStrategy = new SpringAddonsOauth2RedirectStrategy( + addonsProperties.getClient().getOauth2Redirections().getRpInitiatedLogout()); + } - @Override - protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, Authentication authentication) { - if (authentication instanceof OAuth2AuthenticationToken oauth) { - final var postLogoutUri = Optional - .ofNullable(request.getHeader(SpringAddonsOidcClientProperties.POST_LOGOUT_SUCCESS_URI_HEADER)) - .orElse(Optional.ofNullable(request.getParameter(SpringAddonsOidcClientProperties.POST_LOGOUT_SUCCESS_URI_PARAM)).orElse(defaultPostLogoutUri)); + @Override + protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) { + if (authentication instanceof OAuth2AuthenticationToken oauth + && oauth.getPrincipal() instanceof OidcUser oidcUser) { + final var postLogoutUri = Optional + .ofNullable( + request.getHeader(SpringAddonsOidcClientProperties.POST_LOGOUT_SUCCESS_URI_HEADER)) + .orElse(Optional + .ofNullable(request + .getParameter(SpringAddonsOidcClientProperties.POST_LOGOUT_SUCCESS_URI_PARAM)) + .orElse(defaultPostLogoutUri)); - final var clientRegistration = clientRegistrationRepository.findByRegistrationId(oauth.getAuthorizedClientRegistrationId()); - final var uri = StringUtils.hasText(postLogoutUri) - ? uriBuilder.getLogoutRequestUri(clientRegistration, oauth.getName(), Optional.of(URI.create(postLogoutUri))) - : uriBuilder.getLogoutRequestUri(clientRegistration, oauth.getName()); - return uri.orElse(null); - } - return null; + final var clientRegistration = clientRegistrationRepository + .findByRegistrationId(oauth.getAuthorizedClientRegistrationId()); + final var uri = StringUtils.hasText(postLogoutUri) + ? uriBuilder.getLogoutRequestUri(clientRegistration, + oidcUser.getIdToken().getTokenValue(), Optional.of(URI.create(postLogoutUri))) + : uriBuilder.getLogoutRequestUri(clientRegistration, + oidcUser.getIdToken().getTokenValue()); + return uri.orElse(null); } + return null; + } - @Override - public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { - this.redirectStrategy.sendRedirect(request, response, determineTargetUrl(request, response, authentication)); - } + @Override + public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + this.redirectStrategy.sendRedirect(request, response, + determineTargetUrl(request, response, authentication)); + } } diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOAuth2AuthorizationRequestResolver.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOAuth2AuthorizationRequestResolver.java index a431d1fb9..90ac65445 100644 --- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOAuth2AuthorizationRequestResolver.java +++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOAuth2AuthorizationRequestResolver.java @@ -5,7 +5,6 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.stream.Collectors; - import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver; @@ -18,183 +17,212 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; - import com.c4_soft.springaddons.security.oidc.starter.AdditionalParamsAuthorizationRequestCustomizer; import com.c4_soft.springaddons.security.oidc.starter.CompositeOAuth2AuthorizationRequestCustomizer; import com.c4_soft.springaddons.security.oidc.starter.properties.SpringAddonsOidcClientProperties; - import jakarta.servlet.http.HttpServletRequest; /** * Support three features: * - * The post-login URIs are used by the default {@link AuthenticationSuccessHandler} and {@link AuthenticationFailureHandler} + * The post-login URIs are used by the default {@link AuthenticationSuccessHandler} and + * {@link AuthenticationFailureHandler} *

- * When needing fancy request customizers (for instance to add parameters with name or value computed at runtime), you may extend this class - * and override {@link SpringAddonsOAuth2AuthorizationRequestResolver#getOAuth2AuthorizationRequestCustomizer(HttpServletRequest, String)} + * When needing fancy request customizers (for instance to add parameters with name or value + * computed at runtime), you may extend this class and override + * {@link SpringAddonsOAuth2AuthorizationRequestResolver#getOAuth2AuthorizationRequestCustomizer(HttpServletRequest, String)} *

* * @author Jerome Wacongne ch4mp@c4-soft.com - * @see SpringAddonsOidcClientProperties for header and request parameter constants definitions - * @see SpringAddonsOauth2AuthenticationSuccessHandler - * @see SpringAddonsOauth2AuthenticationFailureHandler + * @see SpringAddonsOidcClientProperties for header and request parameter constants definitions + * @see SpringAddonsOauth2AuthenticationSuccessHandler + * @see SpringAddonsOauth2AuthenticationFailureHandler */ -public class SpringAddonsOAuth2AuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver { - private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; - - private final URI clientUri; - private final Map requestCustomizers; - private final ClientRegistrationRepository clientRegistrationRepository; - private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher( - OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + REGISTRATION_ID_URI_VARIABLE_NAME + "}"); - - public SpringAddonsOAuth2AuthorizationRequestResolver( - OAuth2ClientProperties bootClientProperties, - ClientRegistrationRepository clientRegistrationRepository, - SpringAddonsOidcClientProperties addonsClientProperties) { - - this.clientUri = addonsClientProperties.getClientUri(); - - this.requestCustomizers = bootClientProperties.getRegistration().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, registrationEntry -> { - final var requestCustomizer = new CompositeOAuth2AuthorizationRequestCustomizer(); - - final var additionalProperties = addonsClientProperties.getExtraAuthorizationParameters(registrationEntry.getKey()); - if (additionalProperties.size() > 0) { - requestCustomizer.addCustomizer(new AdditionalParamsAuthorizationRequestCustomizer(additionalProperties)); - } - - if (addonsClientProperties.isPkceForced()) { - requestCustomizer.addCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()); - } - - return requestCustomizer; - })); - - this.clientRegistrationRepository = clientRegistrationRepository; - } - - private Optional getFirstParam(HttpServletRequest request, String paramName) { - final var values = request.getParameterValues(paramName); - if (values == null || values.length < 1) { - return Optional.empty(); - } - return Optional.of(values[0]); - } - - private void savePostLoginUrisInSession(HttpServletRequest request) { - final var session = request.getSession(); - Optional.ofNullable( - Optional.ofNullable(request.getHeader(SpringAddonsOidcClientProperties.POST_AUTHENTICATION_SUCCESS_URI_HEADER)) - .orElse(getFirstParam(request, SpringAddonsOidcClientProperties.POST_AUTHENTICATION_SUCCESS_URI_PARAM).orElse(null))) - .filter(StringUtils::hasText).map(URI::create).ifPresent(postLoginSuccessUri -> { - session.setAttribute(SpringAddonsOidcClientProperties.POST_AUTHENTICATION_SUCCESS_URI_SESSION_ATTRIBUTE, postLoginSuccessUri); - }); - - Optional.ofNullable( - Optional.ofNullable(request.getHeader(SpringAddonsOidcClientProperties.POST_AUTHENTICATION_FAILURE_URI_HEADER)) - .orElse(getFirstParam(request, SpringAddonsOidcClientProperties.POST_AUTHENTICATION_FAILURE_URI_PARAM).orElse(null))) - .filter(StringUtils::hasText).map(URI::create).ifPresent(postLoginFailureUri -> { - session.setAttribute(SpringAddonsOidcClientProperties.POST_AUTHENTICATION_FAILURE_URI_SESSION_ATTRIBUTE, postLoginFailureUri); - }); - } - - @Override - public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { - savePostLoginUrisInSession(request); - final var clientRegistrationId = resolveRegistrationId(request); - - final var delegate = getRequestResolver(request, clientRegistrationId); - if (delegate == null) { - return null; - } - - final var resolved = delegate.resolve(request); - final var absolute = toAbsolute(resolved, request); - return absolute; - } - - @Override - public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String clientRegistrationId) { - savePostLoginUrisInSession(request); - - final var delegate = getRequestResolver(request, clientRegistrationId); - if (delegate == null) { - return null; - } - - final var resolved = delegate.resolve(request, clientRegistrationId); - final var absolute = toAbsolute(resolved, request); - return absolute; - } - - /** - * You probably don't need to override this. See getOAuth2AuthorizationRequestCustomizer to add advanced request customizer(s) - * - * @param request - * @param clientRegistrationId - * @return - */ - protected OAuth2AuthorizationRequestResolver getRequestResolver(HttpServletRequest request, String clientRegistrationId) { - final var requestCustomizer = getOAuth2AuthorizationRequestCustomizer(request, clientRegistrationId); - if (requestCustomizer == null) { - return null; - } - - final var delegate = new DefaultOAuth2AuthorizationRequestResolver( - clientRegistrationRepository, - OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI); - delegate.setAuthorizationRequestCustomizer(requestCustomizer); - - return delegate; - } - - /** - * Override this to use a "dynamic" request customizer. Something like: - * - *
-	 * return new CompositeOAuth2AuthorizationRequestCustomizer(getCompositeOAuth2AuthorizationRequestCustomizer(clientRegistrationId), new MyDynamicCustomizer(request), ...);
-	 * 
- * - * @return - */ - protected Consumer getOAuth2AuthorizationRequestCustomizer(HttpServletRequest request, String clientRegistrationId) { - return getCompositeOAuth2AuthorizationRequestCustomizer(clientRegistrationId); - } - - /** - * @return a request customizer adding PKCE token (if activated) and "static" parameters defined in spring-addons properties - */ - protected CompositeOAuth2AuthorizationRequestCustomizer getCompositeOAuth2AuthorizationRequestCustomizer(String clientRegistrationId) { - return this.requestCustomizers.get(clientRegistrationId); - } - - private OAuth2AuthorizationRequest toAbsolute(OAuth2AuthorizationRequest defaultAuthorizationRequest, HttpServletRequest request) { - if (defaultAuthorizationRequest == null || clientUri == null) { - return defaultAuthorizationRequest; - } - - final var original = URI.create(defaultAuthorizationRequest.getRedirectUri()); - final var redirectUri = - UriComponentsBuilder.fromUri(clientUri).path(original.getPath()).query(original.getQuery()).fragment(original.getFragment()).build().toString(); - return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest).redirectUri(redirectUri) - .authorizationRequestUri(defaultAuthorizationRequest.getAuthorizationRequestUri()).build(); - } - - private String resolveRegistrationId(HttpServletRequest request) { - if (this.authorizationRequestMatcher.matches(request)) { - return this.authorizationRequestMatcher.matcher(request).getVariables().get(REGISTRATION_ID_URI_VARIABLE_NAME); - } - return null; - } +public class SpringAddonsOAuth2AuthorizationRequestResolver + implements OAuth2AuthorizationRequestResolver { + private static final String REGISTRATION_ID_URI_VARIABLE_NAME = "registrationId"; + + private final URI clientUri; + private final Map requestCustomizers; + private final ClientRegistrationRepository clientRegistrationRepository; + private final AntPathRequestMatcher authorizationRequestMatcher = new AntPathRequestMatcher( + OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI + "/{" + + REGISTRATION_ID_URI_VARIABLE_NAME + "}"); + + public SpringAddonsOAuth2AuthorizationRequestResolver(OAuth2ClientProperties bootClientProperties, + ClientRegistrationRepository clientRegistrationRepository, + SpringAddonsOidcClientProperties addonsClientProperties) { + + this.clientUri = addonsClientProperties.getClientUri(); + + this.requestCustomizers = bootClientProperties.getRegistration().entrySet().stream() + .collect(Collectors.toMap(Map.Entry::getKey, registrationEntry -> { + final var requestCustomizer = new CompositeOAuth2AuthorizationRequestCustomizer(); + + final var additionalProperties = + addonsClientProperties.getExtraAuthorizationParameters(registrationEntry.getKey()); + if (additionalProperties.size() > 0) { + requestCustomizer.addCustomizer( + new AdditionalParamsAuthorizationRequestCustomizer(additionalProperties)); + } + + if (addonsClientProperties.isPkceForced()) { + requestCustomizer.addCustomizer(OAuth2AuthorizationRequestCustomizers.withPkce()); + } + + return requestCustomizer; + })); + + this.clientRegistrationRepository = clientRegistrationRepository; + } + + private Optional getFirstParam(HttpServletRequest request, String paramName) { + final var values = request.getParameterValues(paramName); + if (values == null || values.length < 1) { + return Optional.empty(); + } + return Optional.of(values[0]); + } + + private void savePostLoginUrisInSession(HttpServletRequest request) { + final var session = request.getSession(); + Optional + .ofNullable(Optional + .ofNullable(request + .getHeader(SpringAddonsOidcClientProperties.POST_AUTHENTICATION_SUCCESS_URI_HEADER)) + .orElse(getFirstParam(request, + SpringAddonsOidcClientProperties.POST_AUTHENTICATION_SUCCESS_URI_PARAM) + .orElse(null))) + .filter(StringUtils::hasText).map(URI::create).ifPresent(postLoginSuccessUri -> { + session.setAttribute( + SpringAddonsOidcClientProperties.POST_AUTHENTICATION_SUCCESS_URI_SESSION_ATTRIBUTE, + postLoginSuccessUri); + }); + + Optional + .ofNullable(Optional + .ofNullable(request + .getHeader(SpringAddonsOidcClientProperties.POST_AUTHENTICATION_FAILURE_URI_HEADER)) + .orElse(getFirstParam(request, + SpringAddonsOidcClientProperties.POST_AUTHENTICATION_FAILURE_URI_PARAM) + .orElse(null))) + .filter(StringUtils::hasText).map(URI::create).ifPresent(postLoginFailureUri -> { + session.setAttribute( + SpringAddonsOidcClientProperties.POST_AUTHENTICATION_FAILURE_URI_SESSION_ATTRIBUTE, + postLoginFailureUri); + }); + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request) { + savePostLoginUrisInSession(request); + final var clientRegistrationId = resolveRegistrationId(request); + + final var delegate = getRequestResolver(request, clientRegistrationId); + if (delegate == null) { + return null; + } + + final var resolved = delegate.resolve(request); + final var absolute = toAbsolute(resolved, request); + return absolute; + } + + @Override + public OAuth2AuthorizationRequest resolve(HttpServletRequest request, + String clientRegistrationId) { + savePostLoginUrisInSession(request); + + final var delegate = getRequestResolver(request, clientRegistrationId); + if (delegate == null) { + return null; + } + + final var resolved = delegate.resolve(request, clientRegistrationId); + final var absolute = toAbsolute(resolved, request); + return absolute; + } + + /** + * You probably don't need to override this. See getOAuth2AuthorizationRequestCustomizer to add + * advanced request customizer(s) + * + * @param request + * @param clientRegistrationId + * @return + */ + protected OAuth2AuthorizationRequestResolver getRequestResolver(HttpServletRequest request, + String clientRegistrationId) { + final var requestCustomizer = + getOAuth2AuthorizationRequestCustomizer(request, clientRegistrationId); + if (requestCustomizer == null) { + return null; + } + + final var delegate = new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, + OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI); + delegate.setAuthorizationRequestCustomizer(requestCustomizer); + + return delegate; + } + + /** + * Override this to use a "dynamic" request customizer. Something like: + * + *
+   * return new CompositeOAuth2AuthorizationRequestCustomizer(getCompositeOAuth2AuthorizationRequestCustomizer(clientRegistrationId), new MyDynamicCustomizer(request), ...);
+   * 
+ * + * @return + */ + protected Consumer getOAuth2AuthorizationRequestCustomizer( + HttpServletRequest request, String clientRegistrationId) { + return getCompositeOAuth2AuthorizationRequestCustomizer(clientRegistrationId); + } + + /** + * @return a request customizer adding PKCE token (if activated) and "static" parameters defined + * in spring-addons properties + */ + protected CompositeOAuth2AuthorizationRequestCustomizer getCompositeOAuth2AuthorizationRequestCustomizer( + String clientRegistrationId) { + return this.requestCustomizers.get(clientRegistrationId); + } + + private OAuth2AuthorizationRequest toAbsolute( + OAuth2AuthorizationRequest defaultAuthorizationRequest, HttpServletRequest request) { + if (defaultAuthorizationRequest == null || clientUri == null) { + return defaultAuthorizationRequest; + } + + final var original = URI.create(defaultAuthorizationRequest.getRedirectUri()); + final var redirectUri = UriComponentsBuilder.fromUri(clientUri).path(original.getPath()) + .query(original.getQuery()).fragment(original.getFragment()).build().toString(); + + return OAuth2AuthorizationRequest.from(defaultAuthorizationRequest).redirectUri(redirectUri) + .build(); + } + + private String resolveRegistrationId(HttpServletRequest request) { + if (this.authorizationRequestMatcher.matches(request)) { + return this.authorizationRequestMatcher.matcher(request).getVariables() + .get(REGISTRATION_ID_URI_VARIABLE_NAME); + } + return null; + } } diff --git a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientWithLoginBeans.java b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientWithLoginBeans.java index 1dcfc9608..e6a86ec59 100644 --- a/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientWithLoginBeans.java +++ b/spring-addons-starter-oidc/src/main/java/com/c4_soft/springaddons/security/oidc/starter/synchronised/client/SpringAddonsOidcClientWithLoginBeans.java @@ -2,7 +2,6 @@ import java.util.ArrayList; import java.util.Optional; - import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.ImportAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; @@ -27,12 +26,12 @@ import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.web.filter.CorsFilter; import org.springframework.web.util.UriComponentsBuilder; - import com.c4_soft.springaddons.security.oidc.starter.ClaimSetAuthoritiesConverter; import com.c4_soft.springaddons.security.oidc.starter.ConfigurableClaimSetAuthoritiesConverter; import com.c4_soft.springaddons.security.oidc.starter.LogoutRequestUriBuilder; @@ -48,7 +47,6 @@ import com.c4_soft.springaddons.security.oidc.starter.properties.condition.configuration.IsClientWithLoginCondition; import com.c4_soft.springaddons.security.oidc.starter.synchronised.ServletConfigurationSupport; import com.c4_soft.springaddons.security.oidc.starter.synchronised.SpringAddonsOidcBeans; - import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; @@ -56,23 +54,31 @@ /** * The following {@link ConditionalOnMissingBean @ConditionalOnMissingBeans} are auto-configured *
    - *
  • springAddonsClientFilterChain: a {@link SecurityFilterChain}. Instantiated only if "com.c4-soft.springaddons.oidc.client.security-matchers" property has - * at least one entry. If defined, it is with highest precedence, to ensure that all routes defined in this security matcher property are intercepted by this - * filter-chain.
  • - *
  • oAuth2AuthorizationRequestResolver: a {@link OAuth2AuthorizationRequestResolver}. Default instance is a - * {@link SpringAddonsOAuth2AuthorizationRequestResolver} which sets the client hostname in the redirect URI with - * {@link SpringAddonsOidcClientProperties#clientUri SpringAddonsOidcClientProperties#client-uri}
  • - *
  • logoutRequestUriBuilder: builder for RP-Initiated Logout queries, taking - * configuration from properties for OIDC providers which do not strictly comply with the spec: logout URI not provided by OIDC conf or non standard parameter - * names (Auth0 and Cognito are samples of such OPs)
  • - *
  • logoutSuccessHandler: a {@link LogoutSuccessHandler}. Default instance is a {@link SpringAddonsLogoutSuccessHandler} which logs a user out from the last - * authorization server he logged on.
  • - *
  • authoritiesConverter: an {@link ClaimSetAuthoritiesConverter}. Default instance is a {@link ConfigurableClaimSetAuthoritiesConverter} which reads - * spring-addons {@link SpringAddonsOidcProperties}
  • - *
  • clientAuthorizePostProcessor: a {@link ClientExpressionInterceptUrlRegistryPostProcessor} post processor to fine tune access control from java - * configuration. It applies to all routes not listed in "permit-all" property configuration. Default requires users to be authenticated.
  • - *
  • clientHttpPostProcessor: a {@link ClientSynchronizedHttpSecurityPostProcessor} to override anything from above auto-configuration. It is called just - * before the security filter-chain is returned. Default is a no-op.
  • + *
  • springAddonsClientFilterChain: a {@link SecurityFilterChain}. Instantiated only if + * "com.c4-soft.springaddons.oidc.client.security-matchers" property has at least one entry. If + * defined, it is with highest precedence, to ensure that all routes defined in this security + * matcher property are intercepted by this filter-chain.
  • + *
  • oAuth2AuthorizationRequestResolver: a {@link OAuth2AuthorizationRequestResolver}. Default + * instance is a {@link SpringAddonsOAuth2AuthorizationRequestResolver} which sets the client + * hostname in the redirect URI with {@link SpringAddonsOidcClientProperties#clientUri + * SpringAddonsOidcClientProperties#client-uri}
  • + *
  • logoutRequestUriBuilder: builder for + * RP-Initiated Logout + * queries, taking configuration from properties for OIDC providers which do not strictly comply + * with the spec: logout URI not provided by OIDC conf or non standard parameter names (Auth0 and + * Cognito are samples of such OPs)
  • + *
  • logoutSuccessHandler: a {@link LogoutSuccessHandler}. Default instance is a + * {@link SpringAddonsLogoutSuccessHandler} which logs a user out from the last authorization server + * he logged on.
  • + *
  • authoritiesConverter: an {@link ClaimSetAuthoritiesConverter}. Default instance is a + * {@link ConfigurableClaimSetAuthoritiesConverter} which reads spring-addons + * {@link SpringAddonsOidcProperties}
  • + *
  • clientAuthorizePostProcessor: a {@link ClientExpressionInterceptUrlRegistryPostProcessor} + * post processor to fine tune access control from java configuration. It applies to all routes not + * listed in "permit-all" property configuration. Default requires users to be authenticated.
  • + *
  • clientHttpPostProcessor: a {@link ClientSynchronizedHttpSecurityPostProcessor} to override + * anything from above auto-configuration. It is called just before the security filter-chain is + * returned. Default is a no-op.
  • *
* * @author Jerome Wacongne ch4mp@c4-soft.com @@ -85,64 +91,74 @@ @Slf4j public class SpringAddonsOidcClientWithLoginBeans { - /** - *

- * Instantiated only if "com.c4-soft.springaddons.oidc.client.security-matchers" property has at least one entry. If defined, it is with higher precedence - * than resource server one. - *

- * It defines: - *
    - *
  • If the path to login page was provided in conf, a @Controller must be provided to handle it. Otherwise Spring Boot default generated one is used - * (be aware that it does not work when bound to 80 or 8080 with SSL enabled, so, in that case, use another port or define a login path and a controller to - * handle it)
  • - *
  • logout (using {@link SpringAddonsLogoutSuccessHandler} by default)
  • - *
  • forces SSL usage if it is enabled
  • properties - *
  • CSRF protection as defined in spring-addons client properties (enabled by default in this filter-chain).
  • - *
  • allow access to unauthorized requests to path matchers listed in spring-security client "permit-all" property
  • - *
  • as usual, apply {@link ClientExpressionInterceptUrlRegistryPostProcessor} for access control configuration from Java conf and - * {@link ClientSynchronizedHttpSecurityPostProcessor} to override anything from the auto-configuration listed above
  • - *
- * - * @param http the security filter-chain builder to configure - * @param serverProperties Spring Boot standard server properties - * @param authorizationRequestResolver the authorization request resolver to use. By default {@link SpringAddonsOAuth2AuthorizationRequestResolver} (adds - * authorization request parameters defined in properties and builds absolutes callback URI) - * @param preAuthorizationCodeRedirectStrategy the redirection strategy to use for authorization-code request - * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use. Default is {@link SpringAddonsAuthenticationEntryPoint} - * @param authenticationSuccessHandler the authentication success handler to use. Default is a {@link SpringAddonsOauth2AuthenticationSuccessHandler} - * @param authenticationFailureHandler the authentication failure handler to use. Default is a {@link SpringAddonsOauth2AuthenticationFailureHandler} - * @param invalidSessionStrategy default redirects to login, unless another status is set in - * com.c4-soft.springaddons.oidc.client.oauth2-redirections.invalid-session-strategy - * @param logoutSuccessHandler Defaulted to {@link SpringAddonsLogoutSuccessHandler} which can handle "almost" RP Initiated Logout conformant OPs (like - * Auth0 and Cognito). Default is a {@link SpringAddonsLogoutSuccessHandler} - * @param addonsProperties {@link SpringAddonsOAuth2ClientProperties spring-addons client properties} - * @param authorizePostProcessor post process authorization after "permit-all" configuration was applied (default is "isAuthenticated()" to everything that - * was not matched) - * @param httpPostProcessor post process the "http" builder just before it is returned (enables to override anything from the auto-configuration) - * spring-addons client properties} - * @param oidcBackChannelLogoutHandler if present, Back-Channel Logout is enabled. A default {@link OidcBackChannelLogoutHandler} is provided if - * com.c4-soft.springaddons.oidc.client.back-channel-logout.enabled is true - * @return a security filter-chain scoped to specified security-matchers and adapted to OAuth2 clients - * @throws Exception in case of miss-configuration - */ - @Order(Ordered.LOWEST_PRECEDENCE - 1) - @Bean - SecurityFilterChain springAddonsClientFilterChain( - HttpSecurity http, - ServerProperties serverProperties, - PreAuthorizationCodeRedirectStrategy preAuthorizationCodeRedirectStrategy, - OAuth2AuthorizationRequestResolver authorizationRequestResolver, - AuthenticationEntryPoint authenticationEntryPoint, - AuthenticationSuccessHandler authenticationSuccessHandler, - AuthenticationFailureHandler authenticationFailureHandler, - InvalidSessionStrategy invalidSessionStrategy, - LogoutSuccessHandler logoutSuccessHandler, - SpringAddonsOidcProperties addonsProperties, - ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, - ClientSynchronizedHttpSecurityPostProcessor httpPostProcessor, - Optional oidcBackChannelLogoutHandler) - throws Exception { - // @formatter:off + /** + *

+ * Instantiated only if "com.c4-soft.springaddons.oidc.client.security-matchers" property has at + * least one entry. If defined, it is with higher precedence than resource server one. + *

+ * It defines: + *
    + *
  • If the path to login page was provided in conf, a @Controller must be provided to + * handle it. Otherwise Spring Boot default generated one is used (be aware that it does not work + * when bound to 80 or 8080 with SSL enabled, so, in that case, use another port or define a login + * path and a controller to handle it)
  • + *
  • logout (using {@link SpringAddonsLogoutSuccessHandler} by default)
  • + *
  • forces SSL usage if it is enabled
  • properties + *
  • CSRF protection as defined in spring-addons client properties (enabled by default in + * this filter-chain).
  • + *
  • allow access to unauthorized requests to path matchers listed in spring-security + * client "permit-all" property
  • + *
  • as usual, apply {@link ClientExpressionInterceptUrlRegistryPostProcessor} for access + * control configuration from Java conf and {@link ClientSynchronizedHttpSecurityPostProcessor} to + * override anything from the auto-configuration listed above
  • + *
+ * + * @param http the security filter-chain builder to configure + * @param serverProperties Spring Boot standard server properties + * @param authorizationRequestResolver the authorization request resolver to use. By default + * {@link SpringAddonsOAuth2AuthorizationRequestResolver} (adds authorization request + * parameters defined in properties and builds absolutes callback URI) + * @param preAuthorizationCodeRedirectStrategy the redirection strategy to use for + * authorization-code request + * @param authenticationEntryPoint the {@link AuthenticationEntryPoint} to use. Default is + * {@link SpringAddonsAuthenticationEntryPoint} + * @param authenticationSuccessHandler the authentication success handler to use. Default is a + * {@link SpringAddonsOauth2AuthenticationSuccessHandler} + * @param authenticationFailureHandler the authentication failure handler to use. Default is a + * {@link SpringAddonsOauth2AuthenticationFailureHandler} + * @param invalidSessionStrategy default redirects to login, unless another status is set in + * com.c4-soft.springaddons.oidc.client.oauth2-redirections.invalid-session-strategy + * @param logoutSuccessHandler Defaulted to {@link SpringAddonsLogoutSuccessHandler} which can + * handle "almost" RP Initiated Logout conformant OPs (like Auth0 and Cognito). Default is + * a {@link SpringAddonsLogoutSuccessHandler} + * @param addonsProperties {@link SpringAddonsOAuth2ClientProperties spring-addons client + * properties} + * @param authorizePostProcessor post process authorization after "permit-all" configuration was + * applied (default is "isAuthenticated()" to everything that was not matched) + * @param httpPostProcessor post process the "http" builder just before it is returned (enables to + * override anything from the auto-configuration) spring-addons client properties} + * @param oidcBackChannelLogoutHandler if present, Back-Channel Logout is enabled. A default + * {@link OidcBackChannelLogoutHandler} is provided if + * com.c4-soft.springaddons.oidc.client.back-channel-logout.enabled is true + * @return a security filter-chain scoped to specified security-matchers and adapted to OAuth2 + * clients + * @throws Exception in case of miss-configuration + */ + @Order(Ordered.LOWEST_PRECEDENCE - 1) + @Bean + SecurityFilterChain springAddonsClientFilterChain(HttpSecurity http, + ServerProperties serverProperties, + PreAuthorizationCodeRedirectStrategy preAuthorizationCodeRedirectStrategy, + OAuth2AuthorizationRequestResolver authorizationRequestResolver, + AuthenticationEntryPoint authenticationEntryPoint, + AuthenticationSuccessHandler authenticationSuccessHandler, + AuthenticationFailureHandler authenticationFailureHandler, + InvalidSessionStrategy invalidSessionStrategy, Optional logoutHandler, + LogoutSuccessHandler logoutSuccessHandler, SpringAddonsOidcProperties addonsProperties, + ClientExpressionInterceptUrlRegistryPostProcessor authorizePostProcessor, + ClientSynchronizedHttpSecurityPostProcessor httpPostProcessor, + Optional oidcBackChannelLogoutHandler) throws Exception { + // @formatter:off log.info("Applying client OAuth2 configuration for: {}", addonsProperties.getClient().getSecurityMatchers()); http.securityMatcher(addonsProperties.getClient().getSecurityMatchers().toArray(new String[] {})); @@ -168,181 +184,191 @@ SecurityFilterChain springAddonsClientFilterChain( }); // @formatter:on - if (oidcBackChannelLogoutHandler.isPresent()) { - http.oidcLogout(ol -> ol.backChannel(bc -> bc.logoutHandler(oidcBackChannelLogoutHandler.get()))); - } + if (oidcBackChannelLogoutHandler.isPresent()) { + http.oidcLogout( + ol -> ol.backChannel(bc -> bc.logoutHandler(oidcBackChannelLogoutHandler.get()))); + } - ServletConfigurationSupport.configureClient(http, serverProperties, addonsProperties, authorizePostProcessor, httpPostProcessor); + ServletConfigurationSupport.configureClient(http, serverProperties, addonsProperties, + authorizePostProcessor, httpPostProcessor); - return http.build(); - } + return http.build(); + } - /** - * Use a {@link SpringAddonsOAuth2AuthorizationRequestResolver} which: - *
    - *
  • takes hostname and port from configuration properties (and works even if SSL is enabled on port 8080)
  • - *
  • spport defining additionl authorization request parameters from properties
  • - *
- * - * @param bootClientProperties "standard" Spring Boot OAuth2 client properties - * @param clientRegistrationRepository - * @param addonsProperties "spring-addons" OAuth2 client properties - * @return {@link SpringAddonsOAuth2AuthorizationRequestResolver} - */ - @ConditionalOnMissingBean - @Bean - OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver( - OAuth2ClientProperties bootClientProperties, - ClientRegistrationRepository clientRegistrationRepository, - SpringAddonsOidcProperties addonsProperties) { - return new SpringAddonsOAuth2AuthorizationRequestResolver(bootClientProperties, clientRegistrationRepository, addonsProperties.getClient()); - } + /** + * Use a {@link SpringAddonsOAuth2AuthorizationRequestResolver} which: + *
    + *
  • takes hostname and port from configuration properties (and works even if SSL is enabled on + * port 8080)
  • + *
  • spport defining additionl authorization request parameters from properties
  • + *
+ * + * @param bootClientProperties "standard" Spring Boot OAuth2 client properties + * @param clientRegistrationRepository + * @param addonsProperties "spring-addons" OAuth2 client properties + * @return {@link SpringAddonsOAuth2AuthorizationRequestResolver} + */ + @ConditionalOnMissingBean + @Bean + OAuth2AuthorizationRequestResolver oAuth2AuthorizationRequestResolver( + OAuth2ClientProperties bootClientProperties, + ClientRegistrationRepository clientRegistrationRepository, + SpringAddonsOidcProperties addonsProperties) { + return new SpringAddonsOAuth2AuthorizationRequestResolver(bootClientProperties, + clientRegistrationRepository, addonsProperties.getClient()); + } - /** - * Build logout request for RP-Initiated Logout. It works with most OIDC - * provider: those complying with the spec (Keycloak for instance), off course, but also those which are close enough to it (Auth0, Cognito, ...) - * - * @param addonsProperties {@link SpringAddonsOAuth2ClientProperties} to pick logout configuration for divergence to the standard (logout URI not provided - * in .well-known/openid-configuration and non-conform parameter names) - * @return {@link SpringAddonsOAuth2LogoutRequestUriBuilder] - */ - @ConditionalOnMissingBean - @Bean - LogoutRequestUriBuilder logoutRequestUriBuilder(SpringAddonsOidcProperties addonsProperties) { - return new SpringAddonsOAuth2LogoutRequestUriBuilder(addonsProperties.getClient()); - } + /** + * Build logout request for + * RP-Initiated + * Logout. It works with most OIDC provider: those complying with the spec (Keycloak for + * instance), off course, but also those which are close enough to it (Auth0, Cognito, ...) + * + * @param addonsProperties {@link SpringAddonsOAuth2ClientProperties} to pick logout configuration + * for divergence to the standard (logout URI not provided in + * .well-known/openid-configuration and non-conform parameter names) + * @return {@link SpringAddonsOAuth2LogoutRequestUriBuilder] + */ + @ConditionalOnMissingBean + @Bean + LogoutRequestUriBuilder logoutRequestUriBuilder(SpringAddonsOidcProperties addonsProperties) { + return new SpringAddonsOAuth2LogoutRequestUriBuilder(addonsProperties.getClient()); + } - /** - * Single tenant logout handler for OIDC provider complying to RP-Initiated - * Logout (or approximately complying to it like Auth0 or Cognito) - * - * @param logoutRequestUriBuilder delegate doing the smart job - * @param clientRegistrationRepository - * @param addonsProperties - * @return {@link SpringAddonsLogoutSuccessHandler} - */ - @ConditionalOnMissingBean - @Bean - LogoutSuccessHandler logoutSuccessHandler( - LogoutRequestUriBuilder logoutRequestUriBuilder, - ClientRegistrationRepository clientRegistrationRepository, - SpringAddonsOidcProperties addonsProperties) { - return new SpringAddonsLogoutSuccessHandler(logoutRequestUriBuilder, clientRegistrationRepository, addonsProperties); - } + /** + * Single tenant logout handler for OIDC provider complying to + * RP-Initiated Logout + * (or approximately complying to it like Auth0 or Cognito) + * + * @param logoutRequestUriBuilder delegate doing the smart job + * @param clientRegistrationRepository + * @param addonsProperties + * @return {@link SpringAddonsLogoutSuccessHandler} + */ + @ConditionalOnMissingBean + @Bean + LogoutSuccessHandler logoutSuccessHandler(LogoutRequestUriBuilder logoutRequestUriBuilder, + ClientRegistrationRepository clientRegistrationRepository, + SpringAddonsOidcProperties addonsProperties) { + return new SpringAddonsLogoutSuccessHandler(logoutRequestUriBuilder, + clientRegistrationRepository, addonsProperties); + } - /** - * @return a Post processor for access control in Java configuration which requires users to be authenticated. It is called after "permit-all" configuration - * property was applied. - */ - @ConditionalOnMissingBean - @Bean - ClientExpressionInterceptUrlRegistryPostProcessor clientAuthorizePostProcessor() { - return registry -> registry.anyRequest().authenticated(); - } + /** + * @return a Post processor for access control in Java configuration which requires users to be + * authenticated. It is called after "permit-all" configuration property was applied. + */ + @ConditionalOnMissingBean + @Bean + ClientExpressionInterceptUrlRegistryPostProcessor clientAuthorizePostProcessor() { + return registry -> registry.anyRequest().authenticated(); + } - /** - * @return a no-op post processor - */ - @ConditionalOnMissingBean - @Bean - ClientSynchronizedHttpSecurityPostProcessor clientHttpPostProcessor() { - return http -> http; - } + /** + * @return a no-op post processor + */ + @ConditionalOnMissingBean + @Bean + ClientSynchronizedHttpSecurityPostProcessor clientHttpPostProcessor() { + return http -> http; + } - @ConditionalOnMissingBean - @Bean - PreAuthorizationCodeRedirectStrategy authorizationCodeRedirectStrategy(SpringAddonsOidcProperties addonsProperties) { - return new SpringAddonsPreAuthorizationCodeRedirectStrategy(addonsProperties.getClient().getOauth2Redirections().getPreAuthorizationCode()); - } + @ConditionalOnMissingBean + @Bean + PreAuthorizationCodeRedirectStrategy authorizationCodeRedirectStrategy( + SpringAddonsOidcProperties addonsProperties) { + return new SpringAddonsPreAuthorizationCodeRedirectStrategy( + addonsProperties.getClient().getOauth2Redirections().getPreAuthorizationCode()); + } - public static class SpringAddonsPreAuthorizationCodeRedirectStrategy extends SpringAddonsOauth2RedirectStrategy - implements - PreAuthorizationCodeRedirectStrategy { - public SpringAddonsPreAuthorizationCodeRedirectStrategy(HttpStatus defaultStatus) { - super(defaultStatus); - } + public static class SpringAddonsPreAuthorizationCodeRedirectStrategy + extends SpringAddonsOauth2RedirectStrategy implements PreAuthorizationCodeRedirectStrategy { + public SpringAddonsPreAuthorizationCodeRedirectStrategy(HttpStatus defaultStatus) { + super(defaultStatus); } + } - @ConditionalOnMissingBean(InvalidSessionStrategy.class) - @Bean - InvalidSessionStrategy invalidSessionStrategy(SpringAddonsOidcProperties addonsProperties) { - final var location = addonsProperties - .getClient() - .getLoginUri() - .orElse( - UriComponentsBuilder - .fromUri(addonsProperties.getClient().getClientUri()) - .pathSegment(addonsProperties.getClient().getClientUri().getPath(), "/login") - .build() - .toUri()) - .toString(); - log - .debug( - "Invalid session. Returning %d and request authentication at %s" - .formatted(addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy().value(), location)); - - if (addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy() == HttpStatus.FOUND) { - return new SimpleRedirectInvalidSessionStrategy(location); - } + @ConditionalOnMissingBean(InvalidSessionStrategy.class) + @Bean + InvalidSessionStrategy invalidSessionStrategy(SpringAddonsOidcProperties addonsProperties) { + final var location = addonsProperties.getClient().getLoginUri() + .orElse(UriComponentsBuilder.fromUri(addonsProperties.getClient().getClientUri()) + .pathSegment(addonsProperties.getClient().getClientUri().getPath(), "/login").build() + .toUri()) + .toString(); + log.debug("Invalid session. Returning %d and request authentication at %s".formatted( + addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy().value(), + location)); - return (HttpServletRequest request, HttpServletResponse response) -> { - response.setStatus(addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy().value()); - response.setHeader(HttpHeaders.LOCATION, location); - if (addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy().is4xxClientError() || addonsProperties - .getClient() - .getOauth2Redirections() - .getInvalidSessionStrategy() - .is5xxServerError()) { - response.getOutputStream().write("Invalid session. Please authenticate at %s".formatted(location).getBytes()); - } - response.flushBuffer(); - }; + if (addonsProperties.getClient().getOauth2Redirections() + .getInvalidSessionStrategy() == HttpStatus.FOUND) { + return new SimpleRedirectInvalidSessionStrategy(location); } - @Conditional(DefaultAuthenticationEntryPointCondition.class) - @Bean - AuthenticationEntryPoint authenticationEntryPoint(SpringAddonsOidcProperties addonsProperties) { - return new SpringAddonsAuthenticationEntryPoint(addonsProperties.getClient()); - } + return (HttpServletRequest request, HttpServletResponse response) -> { + response.setStatus( + addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy().value()); + response.setHeader(HttpHeaders.LOCATION, location); + if (addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy() + .is4xxClientError() + || addonsProperties.getClient().getOauth2Redirections().getInvalidSessionStrategy() + .is5xxServerError()) { + response.getOutputStream() + .write("Invalid session. Please authenticate at %s".formatted(location).getBytes()); + } + response.flushBuffer(); + }; + } - @Conditional(DefaultAuthenticationSuccessHandlerCondition.class) - @Bean - AuthenticationSuccessHandler authenticationSuccessHandler(SpringAddonsOidcProperties addonsProperties) { - return new SpringAddonsOauth2AuthenticationSuccessHandler(addonsProperties); - } + @Conditional(DefaultAuthenticationEntryPointCondition.class) + @Bean + AuthenticationEntryPoint authenticationEntryPoint(SpringAddonsOidcProperties addonsProperties) { + return new SpringAddonsAuthenticationEntryPoint(addonsProperties.getClient()); + } - @Conditional(DefaultAuthenticationFailureHandlerCondition.class) - @Bean - AuthenticationFailureHandler authenticationFailureHandler(SpringAddonsOidcProperties addonsProperties) { - return new SpringAddonsOauth2AuthenticationFailureHandler(addonsProperties); - } + @Conditional(DefaultAuthenticationSuccessHandlerCondition.class) + @Bean + AuthenticationSuccessHandler authenticationSuccessHandler( + SpringAddonsOidcProperties addonsProperties) { + return new SpringAddonsOauth2AuthenticationSuccessHandler(addonsProperties); + } - /** - * FIXME: use only the new CORS properties at next major release - */ - @Conditional(DefaultCorsFilterCondition.class) - @Bean - CorsFilter corsFilter(SpringAddonsOidcProperties addonsProperties) { - final var corsProps = new ArrayList<>(addonsProperties.getCors()); - final var deprecatedClientCorsProps = addonsProperties.getClient().getCors(); - corsProps.addAll(deprecatedClientCorsProps); + @Conditional(DefaultAuthenticationFailureHandlerCondition.class) + @Bean + AuthenticationFailureHandler authenticationFailureHandler( + SpringAddonsOidcProperties addonsProperties) { + return new SpringAddonsOauth2AuthenticationFailureHandler(addonsProperties); + } - return ServletConfigurationSupport.getCorsFilterBean(corsProps); - } + /** + * FIXME: use only the new CORS properties at next major release + */ + @Conditional(DefaultCorsFilterCondition.class) + @Bean + CorsFilter corsFilter(SpringAddonsOidcProperties addonsProperties) { + final var corsProps = new ArrayList<>(addonsProperties.getCors()); + final var deprecatedClientCorsProps = addonsProperties.getClient().getCors(); + corsProps.addAll(deprecatedClientCorsProps); - @Conditional(DefaultOidcSessionRegistryCondition.class) - @Bean - OidcSessionRegistry oidcSessionRegistry() { - return new InMemoryOidcSessionRegistry(); - } + return ServletConfigurationSupport.getCorsFilterBean(corsProps); + } - @Conditional(DefaultOidcBackChannelLogoutHandlerCondition.class) - @Bean - OidcBackChannelLogoutHandler oidcBackChannelLogoutHandler(OidcSessionRegistry sessionRegistry, SpringAddonsOidcProperties addonsProperties) { - OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(sessionRegistry); - addonsProperties.getClient().getBackChannelLogout().getInternalLogoutUri().ifPresent(logoutHandler::setLogoutUri); - addonsProperties.getClient().getBackChannelLogout().getCookieName().ifPresent(logoutHandler::setSessionCookieName); - return logoutHandler; - } + @Conditional(DefaultOidcSessionRegistryCondition.class) + @Bean + OidcSessionRegistry oidcSessionRegistry() { + return new InMemoryOidcSessionRegistry(); + } + + @Conditional(DefaultOidcBackChannelLogoutHandlerCondition.class) + @Bean + OidcBackChannelLogoutHandler oidcBackChannelLogoutHandler(OidcSessionRegistry sessionRegistry, + SpringAddonsOidcProperties addonsProperties) { + OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(sessionRegistry); + addonsProperties.getClient().getBackChannelLogout().getInternalLogoutUri() + .ifPresent(logoutHandler::setLogoutUri); + addonsProperties.getClient().getBackChannelLogout().getCookieName() + .ifPresent(logoutHandler::setSessionCookieName); + return logoutHandler; + } }