From 97253c9293030002a34f0ea561004e53f32eac37 Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Tue, 21 Jun 2022 17:12:11 -0600 Subject: [PATCH] Add SecurityContextHolderStrategy Java Configuration for Saml2 Issue gh-11061 --- .../saml2/Saml2LoginConfigurer.java | 1 + .../saml2/Saml2LogoutConfigurer.java | 13 +++++++--- .../saml2/Saml2LoginConfigurerTests.java | 26 +++++++++++++++++++ .../saml2/Saml2LogoutConfigurerTests.java | 20 ++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) 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 a4cfb815dc..578c1d295e 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 @@ -233,6 +233,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 5a974231ae..b3ed0475ee 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 @@ -34,7 +34,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; @@ -255,6 +255,7 @@ public final class Saml2LogoutConfigurer> Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(registrations, this.logoutRequestConfigurer.logoutRequestValidator(), logoutResponseResolver, logoutHandlers); filter.setLogoutRequestMatcher(createLogoutRequestMatcher()); + filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); return postProcess(filter); } @@ -278,7 +279,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); } @@ -490,9 +491,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 a7b2a157a8..36ee156e92 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 @@ -53,6 +53,7 @@ import org.springframework.security.authentication.AuthenticationServiceExceptio import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.Customizer; 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.annotation.web.configuration.WebSecurityConfigurerAdapter; @@ -64,6 +65,8 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; +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; @@ -112,10 +115,13 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; 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; @@ -191,6 +197,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 33a2207386..072df740b8 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 @@ -42,11 +42,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; @@ -270,6 +272,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 {