diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java index 4b374cacce..d9da507d99 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java @@ -243,6 +243,7 @@ public final class Saml2LoginConfigurer> relyingPartyRegistrationRepository(http); this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http), this.loginProcessingUrl); + this.saml2WebSsoAuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); setAuthenticationRequestRepository(http, this.saml2WebSsoAuthenticationFilter); setAuthenticationFilter(this.saml2WebSsoAuthenticationFilter); super.loginProcessingUrl(this.loginProcessingUrl); 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 58e17716e9..bd7e13ece1 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 @@ -31,7 +31,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutRequestValidator; import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutResponseValidator; @@ -248,6 +248,7 @@ public final class Saml2LogoutConfigurer> Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(registrations, this.logoutRequestConfigurer.logoutRequestValidator(), logoutResponseResolver, logoutHandlers); filter.setLogoutRequestMatcher(createLogoutRequestMatcher()); + filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); return postProcess(filter); } @@ -271,7 +272,7 @@ public final class Saml2LogoutConfigurer> private RequestMatcher createLogoutMatcher() { RequestMatcher logout = new AntPathRequestMatcher(this.logoutUrl, "POST"); - RequestMatcher saml2 = new Saml2RequestMatcher(); + RequestMatcher saml2 = new Saml2RequestMatcher(getSecurityContextHolderStrategy()); return new AndRequestMatcher(logout, saml2); } @@ -464,9 +465,15 @@ public final class Saml2LogoutConfigurer> private static class Saml2RequestMatcher implements RequestMatcher { + private final SecurityContextHolderStrategy securityContextHolderStrategy; + + Saml2RequestMatcher(SecurityContextHolderStrategy securityContextHolderStrategy) { + this.securityContextHolderStrategy = securityContextHolderStrategy; + } + @Override public boolean matches(HttpServletRequest request) { - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication(); if (authentication == null) { return false; } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java index a92d6060bb..532fe482c0 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java @@ -45,6 +45,7 @@ import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; @@ -53,6 +54,8 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContextChangedListener; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2ErrorCodes; import org.springframework.security.saml2.core.Saml2Utils; import org.springframework.security.saml2.core.TestSaml2X509Credentials; @@ -91,10 +94,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.springframework.security.config.Customizer.withDefaults; +import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; @@ -162,6 +168,26 @@ public class Saml2LoginConfigurerTests { // @formatter:on } + @Test + public void saml2LoginWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { + this.spring + .register(Saml2LoginConfig.class, SecurityContextChangedListenerConfig.class, ResourceController.class) + .autowire(); + // @formatter:off + MockHttpSession session = (MockHttpSession) this.mvc + .perform(post("/login/saml2/sso/registration-id") + .param("SAMLResponse", SIGNED_RESPONSE)) + .andExpect(redirectedUrl("/")).andReturn().getRequest().getSession(false); + this.mvc.perform(get("/").session(session)) + .andExpect(content().string("test@saml.user")); + // @formatter:on + SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class); + verify(strategy, atLeastOnce()).getContext(); + SecurityContextChangedListener listener = this.spring.getContext() + .getBean(SecurityContextChangedListener.class); + verify(listener, times(2)).securityContextChanged(setAuthentication(Saml2Authentication.class)); + } + @Test public void saml2LoginWhenConfiguringAuthenticationManagerThenTheManagerIsUsed() throws Exception { // setup application context 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 56f3828cb7..0afd37e3f4 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 @@ -43,11 +43,13 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.core.authority.AuthorityUtils; +import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.saml2.core.Saml2Utils; import org.springframework.security.saml2.core.Saml2X509Credential; import org.springframework.security.saml2.core.TestSaml2X509Credentials; @@ -271,6 +273,24 @@ public class Saml2LogoutConfigurerTests { verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); } + @Test + public void saml2LogoutRequestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception { + this.spring.register(Saml2LogoutDefaultsConfig.class, SecurityContextChangedListenerConfig.class).autowire(); + DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user", + Collections.emptyMap()); + principal.setRelyingPartyRegistrationId("get"); + Saml2Authentication user = new Saml2Authentication(principal, "response", + AuthorityUtils.createAuthorityList("ROLE_USER")); + MvcResult result = this.mvc.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest) + .param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg) + .param("Signature", this.apLogoutRequestSignature).with(samlQueryString()).with(authentication(user))) + .andExpect(status().isFound()).andReturn(); + String location = result.getResponse().getHeader("Location"); + assertThat(location).startsWith("https://ap.example.org/logout/saml2/response"); + verify(getBean(LogoutHandler.class)).logout(any(), any(), any()); + verify(getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext(); + } + // gh-11235 @Test public void saml2LogoutRequestWhenLowercaseEncodingThenLogsOutAndSendsLogoutResponse() throws Exception {