From 2affae5b6b1e3ef9d1f033295aecd3df89900ad3 Mon Sep 17 00:00:00 2001 From: Evgeniy Cheban Date: Fri, 18 Apr 2025 05:48:40 +0300 Subject: [PATCH] Consider customization of Saml2LogoutRequestValidatorParametersResolver Closes gh-16840 Signed-off-by: Evgeniy Cheban --- .../saml2/Saml2LogoutConfigurer.java | 34 +++++++++- .../annotation/web/saml2/LogoutRequestDsl.kt | 8 ++- .../saml2/Saml2LogoutConfigurerTests.java | 68 ++++++++++++++++++- 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java index e69f8c825b6..1306a31a334 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -109,6 +109,7 @@ * * @author Josh Cummings * @author Ngoc Nhan + * @author Evgeniy Cheban * @since 5.6 * @see Saml2LogoutConfigurer */ @@ -262,12 +263,25 @@ private Saml2LogoutRequestFilter createLogoutRequestProcessingFilter( LogoutHandler[] logoutHandlers = this.logoutHandlers.toArray(new LogoutHandler[0]); Saml2LogoutResponseResolver logoutResponseResolver = createSaml2LogoutResponseResolver(registrations); Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter( - createSaml2LogoutResponseParametersResolver(registrations), + getLogoutRequestParametersResolver(registrations), this.logoutRequestConfigurer.logoutRequestValidator(), logoutResponseResolver, logoutHandlers); filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); return postProcess(filter); } + private Saml2LogoutRequestValidatorParametersResolver getLogoutRequestParametersResolver( + RelyingPartyRegistrationRepository registrations) { + if (this.logoutRequestConfigurer.logoutRequestParametersResolver != null) { + return this.logoutRequestConfigurer.logoutRequestParametersResolver; + } + Saml2LogoutRequestValidatorParametersResolver logoutRequestResolver = getBeanOrNull( + Saml2LogoutRequestValidatorParametersResolver.class); + if (logoutRequestResolver != null) { + return logoutRequestResolver; + } + return createSaml2LogoutResponseParametersResolver(registrations); + } + private Saml2LogoutRequestValidatorParametersResolver createSaml2LogoutResponseParametersResolver( RelyingPartyRegistrationRepository registrations) { RequestMatcher requestMatcher = createLogoutRequestMatcher(); @@ -352,6 +366,8 @@ public final class LogoutRequestConfigurer { private Saml2LogoutRequestResolver logoutRequestResolver; + private Saml2LogoutRequestValidatorParametersResolver logoutRequestParametersResolver; + private Saml2LogoutRequestRepository logoutRequestRepository = new HttpSessionLogoutRequestRepository(); LogoutRequestConfigurer() { @@ -394,6 +410,20 @@ public LogoutRequestConfigurer logoutRequestResolver(Saml2LogoutRequestResolver return this; } + /** + * Use this {@link Saml2LogoutRequestValidatorParametersResolver} for resolving + * logout request and associated validation parameters. + * @param logoutRequestParametersResolver the + * {@link Saml2LogoutRequestValidatorParametersResolver} to use + * @return the {@link LogoutRequestConfigurer} for further customizations + * @since 6.5 + */ + public LogoutRequestConfigurer logoutRequestParametersResolver( + Saml2LogoutRequestValidatorParametersResolver logoutRequestParametersResolver) { + this.logoutRequestParametersResolver = logoutRequestParametersResolver; + return this; + } + /** * Use this {@link Saml2LogoutRequestRepository} for storing logout requests * @param logoutRequestRepository the {@link Saml2LogoutRequestRepository} to use diff --git a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt index 0a07c15fb5e..de675c83445 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/saml2/LogoutRequestDsl.kt @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,16 +22,20 @@ import org.springframework.security.saml2.provider.service.authentication.logout import org.springframework.security.saml2.provider.service.web.authentication.logout.HttpSessionLogoutRequestRepository import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestValidatorParametersResolver /** * A Kotlin DSL to configure SAML 2.0 Logout Request components using idiomatic Kotlin code. * * @author Josh Cummings + * @author Evgeniy Cheban * @since 6.3 * @property logoutUrl The URL by which the asserting party can send a SAML 2.0 Logout Request. * The Asserting Party should use whatever HTTP method specified in {@link RelyingPartyRegistration#getSingleLogoutServiceBindings()}. * @property logoutRequestValidator the [Saml2LogoutRequestValidator] to use for validating incoming {@code LogoutRequest}s. * @property logoutRequestResolver the [Saml2LogoutRequestResolver] to use for generating outgoing {@code LogoutRequest}s. + * @property logoutRequestParametersResolver the [Saml2LogoutRequestValidatorParametersResolver] to use for resolving logout + * request and associated validation parameters. * @property logoutRequestRepository the [Saml2LogoutRequestRepository] to use for storing outgoing {@code LogoutRequest}s for * linking to the corresponding {@code LogoutResponse} from the asserting party */ @@ -40,6 +44,7 @@ class LogoutRequestDsl { var logoutUrl = "/logout/saml2/slo" var logoutRequestValidator: Saml2LogoutRequestValidator? = null var logoutRequestResolver: Saml2LogoutRequestResolver? = null + var logoutRequestParametersResolver: Saml2LogoutRequestValidatorParametersResolver? = null var logoutRequestRepository: Saml2LogoutRequestRepository = HttpSessionLogoutRequestRepository() internal fun get(): (Saml2LogoutConfigurer.LogoutRequestConfigurer) -> Unit { @@ -47,6 +52,7 @@ class LogoutRequestDsl { logoutUrl.also { logoutRequest.logoutUrl(logoutUrl) } logoutRequestValidator?.also { logoutRequest.logoutRequestValidator(logoutRequestValidator) } logoutRequestResolver?.also { logoutRequest.logoutRequestResolver(logoutRequestResolver) } + logoutRequestParametersResolver?.also { logoutRequest.logoutRequestParametersResolver(logoutRequestParametersResolver) } logoutRequestRepository.also { logoutRequest.logoutRequestRepository(logoutRequestRepository) } } } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java index e13bddf7073..3c083b07bb0 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -70,6 +70,7 @@ import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestRepository; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestResolver; +import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutRequestValidatorParametersResolver; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseFilter; import org.springframework.security.saml2.provider.service.web.authentication.logout.Saml2LogoutResponseResolver; import org.springframework.security.web.SecurityFilterChain; @@ -542,6 +543,22 @@ public void saml2LogoutWhenLogoutFilterPostProcessedThenUses() { } + @Test + public void saml2LogoutWhenCustomLogoutRequestParametersResolverBeanThenUses() throws Exception { + this.spring.register(Saml2DefaultsWithLogoutRequestParametersResolverBeanConfig.class).autowire(); + this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())); + verify(Saml2DefaultsWithLogoutRequestParametersResolverBeanConfig.logoutRequestParametersResolver) + .resolve(any(), eq(this.user)); + } + + @Test + public void saml2LogoutWhenCustomLogoutRequestParametersResolverSetThenUses() throws Exception { + this.spring.register(Saml2DefaultsWithLogoutRequestParametersResolverSetConfig.class).autowire(); + this.mvc.perform(post("/logout").with(authentication(this.user)).with(csrf())); + verify(Saml2DefaultsWithLogoutRequestParametersResolverSetConfig.logoutRequestParametersResolver).resolve(any(), + eq(this.user)); + } + private T getBean(Class clazz) { return this.spring.getContext().getBean(clazz); } @@ -723,6 +740,55 @@ Saml2LogoutResponseResolver logoutResponseResolver() { } + @Configuration + @EnableWebSecurity + @Import(Saml2LoginConfigBeans.class) + static class Saml2DefaultsWithLogoutRequestParametersResolverBeanConfig { + + static Saml2LogoutRequestValidatorParametersResolver logoutRequestParametersResolver = mock( + Saml2LogoutRequestValidatorParametersResolver.class); + + @Bean + SecurityFilterChain web(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests((authorize) -> authorize.anyRequest().authenticated()) + .saml2Login(withDefaults()) + .saml2Logout(withDefaults()); + return http.build(); + // @formatter:on + } + + @Bean + Saml2LogoutRequestValidatorParametersResolver logoutRequestParametersResolver() { + return logoutRequestParametersResolver; + } + + } + + @Configuration + @EnableWebSecurity + @Import(Saml2LoginConfigBeans.class) + static class Saml2DefaultsWithLogoutRequestParametersResolverSetConfig { + + static Saml2LogoutRequestValidatorParametersResolver logoutRequestParametersResolver = mock( + Saml2LogoutRequestValidatorParametersResolver.class); + + @Bean + SecurityFilterChain web(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests((authorize) -> authorize.anyRequest().authenticated()) + .saml2Login(withDefaults()) + .saml2Logout((logout) -> logout + .logoutRequest((logoutRequest) -> logoutRequest + .logoutRequestParametersResolver(logoutRequestParametersResolver))); + return http.build(); + // @formatter:on + } + + } + static class Saml2LoginConfigBeans { @Bean