From baf5ebacefa983bb67b7374a9756fb7e9b6793ac Mon Sep 17 00:00:00 2001 From: Andrey Litvitski Date: Sat, 1 Nov 2025 16:52:16 +0300 Subject: [PATCH] Add logoutRequestMatcher to Saml2LogoutConfigurer for custom matching Closes: gh-10821 Signed-off-by: Andrey Litvitski --- .../saml2/Saml2LogoutConfigurer.java | 16 ++++++++ .../saml2/Saml2LogoutConfigurerTests.java | 40 +++++++++++++++++++ 2 files changed, 56 insertions(+) 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 1c519e786cd..b86615befc7 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 @@ -105,6 +105,7 @@ * * @author Josh Cummings * @author Ngoc Nhan + * @author Andrey Litvitski * @since 5.6 * @see Saml2LogoutConfigurer */ @@ -127,6 +128,8 @@ public final class Saml2LogoutConfigurer> private LogoutResponseConfigurer logoutResponseConfigurer; + private RequestMatcher logoutRequestMatcher; + /** * Creates a new instance * @see HttpSecurity#logout(Customizer) @@ -195,6 +198,16 @@ public Saml2LogoutConfigurer logoutResponse( return this; } + /** + * Sets a custom {@link RequestMatcher} to use for SAML logout requests. + * @param logoutRequestMatcher the matcher to use + * @return the {@link Saml2LogoutConfigurer} for further customization + */ + public Saml2LogoutConfigurer logoutRequestMatcher(RequestMatcher logoutRequestMatcher) { + this.logoutRequestMatcher = logoutRequestMatcher; + return this; + } + /** * {@inheritDoc} */ @@ -271,6 +284,9 @@ private Saml2RelyingPartyInitiatedLogoutFilter createRelyingPartyLogoutFilter( } private RequestMatcher createLogoutMatcher() { + if (this.logoutRequestMatcher != null) { + return this.logoutRequestMatcher; + } RequestMatcher logout = getRequestMatcherBuilder().matcher(HttpMethod.POST, this.logoutUrl); RequestMatcher saml2 = new Saml2RequestMatcher(getSecurityContextHolderStrategy()); return new AndRequestMatcher(logout, saml2); 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 aae4bd16caa..c4492e5dcaa 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 @@ -543,6 +543,18 @@ public void saml2LogoutWhenLogoutFilterPostProcessedThenUses() { } + // gh-10821 + @Test + public void saml2LogoutWhenCustomLogoutRequestMatcherThenUsed() throws Exception { + this.spring.register(Saml2LogoutCustomMatcherConfig.class).autowire(); + MvcResult result = this.mvc.perform(post("/saml/custom-logout").with(authentication(this.user)).with(csrf())) + .andExpect(status().isFound()) + .andReturn(); + assertThat(result.getResponse().getHeader("Location")) + .startsWith("https://ap.example.org/logout/saml2/request"); + verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); + } + private T getBean(Class clazz) { return this.spring.getContext().getBean(clazz); } @@ -577,6 +589,34 @@ LogoutHandler logoutHandler() { } + @Configuration + @EnableWebSecurity + @Import(Saml2LoginConfigBeans.class) + static class Saml2LogoutCustomMatcherConfig { + + LogoutHandler mockLogoutHandler = mock(LogoutHandler.class); + + @Bean + SecurityFilterChain web(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()) + .logout((logout) -> logout.addLogoutHandler(this.mockLogoutHandler)) + .saml2Login(withDefaults()) + .saml2Logout((saml2) -> saml2 + .logoutRequestMatcher(pathPattern(HttpMethod.POST, "/saml/custom-logout")) + ); + return http.build(); + // @formatter:on + } + + @Bean + LogoutHandler logoutHandler() { + return this.mockLogoutHandler; + } + + } + @Configuration @EnableWebSecurity @Import(Saml2LoginConfigBeans.class)