Skip to content

Commit 19181a5

Browse files
committed
Add SecurityContextHolderStrategy Java Configuration for Saml2
Issue gh-11061
1 parent e90a11b commit 19181a5

File tree

4 files changed

+57
-3
lines changed

4 files changed

+57
-3
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurer.java

+1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ public void init(B http) throws Exception {
243243
relyingPartyRegistrationRepository(http);
244244
this.saml2WebSsoAuthenticationFilter = new Saml2WebSsoAuthenticationFilter(getAuthenticationConverter(http),
245245
this.loginProcessingUrl);
246+
this.saml2WebSsoAuthenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
246247
setAuthenticationRequestRepository(http, this.saml2WebSsoAuthenticationFilter);
247248
setAuthenticationFilter(this.saml2WebSsoAuthenticationFilter);
248249
super.loginProcessingUrl(this.loginProcessingUrl);

config/src/main/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurer.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
3232
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
3333
import org.springframework.security.core.Authentication;
34-
import org.springframework.security.core.context.SecurityContextHolder;
34+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
3535
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
3636
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutRequestValidator;
3737
import org.springframework.security.saml2.provider.service.authentication.logout.OpenSamlLogoutResponseValidator;
@@ -248,6 +248,7 @@ private Saml2LogoutRequestFilter createLogoutRequestProcessingFilter(
248248
Saml2LogoutRequestFilter filter = new Saml2LogoutRequestFilter(registrations,
249249
this.logoutRequestConfigurer.logoutRequestValidator(), logoutResponseResolver, logoutHandlers);
250250
filter.setLogoutRequestMatcher(createLogoutRequestMatcher());
251+
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
251252
return postProcess(filter);
252253
}
253254

@@ -271,7 +272,7 @@ private LogoutFilter createRelyingPartyLogoutFilter(RelyingPartyRegistrationReso
271272

272273
private RequestMatcher createLogoutMatcher() {
273274
RequestMatcher logout = new AntPathRequestMatcher(this.logoutUrl, "POST");
274-
RequestMatcher saml2 = new Saml2RequestMatcher();
275+
RequestMatcher saml2 = new Saml2RequestMatcher(getSecurityContextHolderStrategy());
275276
return new AndRequestMatcher(logout, saml2);
276277
}
277278

@@ -464,9 +465,15 @@ private Saml2LogoutResponseResolver logoutResponseResolver(
464465

465466
private static class Saml2RequestMatcher implements RequestMatcher {
466467

468+
private final SecurityContextHolderStrategy securityContextHolderStrategy;
469+
470+
Saml2RequestMatcher(SecurityContextHolderStrategy securityContextHolderStrategy) {
471+
this.securityContextHolderStrategy = securityContextHolderStrategy;
472+
}
473+
467474
@Override
468475
public boolean matches(HttpServletRequest request) {
469-
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
476+
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
470477
if (authentication == null) {
471478
return false;
472479
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LoginConfigurerTests.java

+26
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.security.authentication.AuthenticationManager;
4646
import org.springframework.security.authentication.AuthenticationServiceException;
4747
import org.springframework.security.config.Customizer;
48+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
4849
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4950
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
5051
import org.springframework.security.config.test.SpringTestContext;
@@ -53,6 +54,8 @@
5354
import org.springframework.security.core.AuthenticationException;
5455
import org.springframework.security.core.annotation.AuthenticationPrincipal;
5556
import org.springframework.security.core.authority.SimpleGrantedAuthority;
57+
import org.springframework.security.core.context.SecurityContextChangedListener;
58+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5659
import org.springframework.security.saml2.core.Saml2ErrorCodes;
5760
import org.springframework.security.saml2.core.Saml2Utils;
5861
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
@@ -91,10 +94,13 @@
9194
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
9295
import static org.mockito.ArgumentMatchers.any;
9396
import static org.mockito.BDDMockito.given;
97+
import static org.mockito.Mockito.atLeastOnce;
9498
import static org.mockito.Mockito.mock;
9599
import static org.mockito.Mockito.spy;
100+
import static org.mockito.Mockito.times;
96101
import static org.mockito.Mockito.verify;
97102
import static org.springframework.security.config.Customizer.withDefaults;
103+
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
98104
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
99105
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
100106
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
@@ -162,6 +168,26 @@ public void saml2LoginWhenDefaultsThenSaml2AuthenticatedPrincipal() throws Excep
162168
// @formatter:on
163169
}
164170

171+
@Test
172+
public void saml2LoginWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
173+
this.spring
174+
.register(Saml2LoginConfig.class, SecurityContextChangedListenerConfig.class, ResourceController.class)
175+
.autowire();
176+
// @formatter:off
177+
MockHttpSession session = (MockHttpSession) this.mvc
178+
.perform(post("/login/saml2/sso/registration-id")
179+
.param("SAMLResponse", SIGNED_RESPONSE))
180+
.andExpect(redirectedUrl("/")).andReturn().getRequest().getSession(false);
181+
this.mvc.perform(get("/").session(session))
182+
.andExpect(content().string("test@saml.user"));
183+
// @formatter:on
184+
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
185+
verify(strategy, atLeastOnce()).getContext();
186+
SecurityContextChangedListener listener = this.spring.getContext()
187+
.getBean(SecurityContextChangedListener.class);
188+
verify(listener, times(2)).securityContextChanged(setAuthentication(Saml2Authentication.class));
189+
}
190+
165191
@Test
166192
public void saml2LoginWhenConfiguringAuthenticationManagerThenTheManagerIsUsed() throws Exception {
167193
// setup application context

config/src/test/java/org/springframework/security/config/annotation/web/configurers/saml2/Saml2LogoutConfigurerTests.java

+20
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,13 @@
4343
import org.springframework.mock.web.MockHttpSession;
4444
import org.springframework.security.authentication.TestingAuthenticationToken;
4545
import org.springframework.security.config.annotation.ObjectPostProcessor;
46+
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
4647
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4748
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
4849
import org.springframework.security.config.test.SpringTestContext;
4950
import org.springframework.security.config.test.SpringTestContextExtension;
5051
import org.springframework.security.core.authority.AuthorityUtils;
52+
import org.springframework.security.core.context.SecurityContextHolderStrategy;
5153
import org.springframework.security.saml2.core.Saml2Utils;
5254
import org.springframework.security.saml2.core.Saml2X509Credential;
5355
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
@@ -271,6 +273,24 @@ public void saml2LogoutRequestWhenDefaultsThenLogsOutAndSendsLogoutResponse() th
271273
verify(getBean(LogoutHandler.class)).logout(any(), any(), any());
272274
}
273275

276+
@Test
277+
public void saml2LogoutRequestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
278+
this.spring.register(Saml2LogoutDefaultsConfig.class, SecurityContextChangedListenerConfig.class).autowire();
279+
DefaultSaml2AuthenticatedPrincipal principal = new DefaultSaml2AuthenticatedPrincipal("user",
280+
Collections.emptyMap());
281+
principal.setRelyingPartyRegistrationId("get");
282+
Saml2Authentication user = new Saml2Authentication(principal, "response",
283+
AuthorityUtils.createAuthorityList("ROLE_USER"));
284+
MvcResult result = this.mvc.perform(get("/logout/saml2/slo").param("SAMLRequest", this.apLogoutRequest)
285+
.param("RelayState", this.apLogoutRequestRelayState).param("SigAlg", this.apLogoutRequestSigAlg)
286+
.param("Signature", this.apLogoutRequestSignature).with(samlQueryString()).with(authentication(user)))
287+
.andExpect(status().isFound()).andReturn();
288+
String location = result.getResponse().getHeader("Location");
289+
assertThat(location).startsWith("https://ap.example.org/logout/saml2/response");
290+
verify(getBean(LogoutHandler.class)).logout(any(), any(), any());
291+
verify(getBean(SecurityContextHolderStrategy.class), atLeastOnce()).getContext();
292+
}
293+
274294
// gh-11235
275295
@Test
276296
public void saml2LogoutRequestWhenLowercaseEncodingThenLogsOutAndSendsLogoutResponse() throws Exception {

0 commit comments

Comments
 (0)