From 977db36e989c0bafed20e20de6ecb1b43467dbdc Mon Sep 17 00:00:00 2001 From: Filip Hanik Date: Wed, 30 Oct 2019 13:39:35 -0700 Subject: [PATCH] Make the loginProcessingUrl configurable for saml2Login() Fixes gh-7565 https://github.com/spring-projects/spring-security/issues/7565 --- .../saml2/Saml2LoginConfigurer.java | 5 +- .../Saml2WebSsoAuthenticationFilter.java | 16 +++- .../Saml2WebSsoAuthenticationFilterTests.java | 75 +++++++++++++++++++ .../samples/Saml2LoginIntegrationTests.java | 2 - .../samples/config/SecurityConfig.java | 1 + .../samples/config/SecurityConfigTests.java | 33 +++++++- 6 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java 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 2b87c64418f..717aa9e74b9 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 @@ -164,7 +164,10 @@ public void init(B http) throws Exception { this.relyingPartyRegistrationRepository = getSharedOrBean(http, RelyingPartyRegistrationRepository.class); } - Saml2WebSsoAuthenticationFilter webSsoFilter = new Saml2WebSsoAuthenticationFilter(this.relyingPartyRegistrationRepository); + Saml2WebSsoAuthenticationFilter webSsoFilter = new Saml2WebSsoAuthenticationFilter( + this.relyingPartyRegistrationRepository, + this.loginProcessingUrl + ); setAuthenticationFilter(webSsoFilter); super.loginProcessingUrl(this.loginProcessingUrl); diff --git a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java index 6b290ec9e95..9c0d519de39 100644 --- a/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java +++ b/saml2/saml2-service-provider/src/main/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilter.java @@ -44,9 +44,21 @@ public class Saml2WebSsoAuthenticationFilter extends AbstractAuthenticationProce private final RelyingPartyRegistrationRepository relyingPartyRegistrationRepository; public Saml2WebSsoAuthenticationFilter(RelyingPartyRegistrationRepository relyingPartyRegistrationRepository) { - super(DEFAULT_FILTER_PROCESSES_URI); + this(relyingPartyRegistrationRepository, DEFAULT_FILTER_PROCESSES_URI); + } + + public Saml2WebSsoAuthenticationFilter( + RelyingPartyRegistrationRepository relyingPartyRegistrationRepository, + String filterProcessesUrl) { + super(filterProcessesUrl); Assert.notNull(relyingPartyRegistrationRepository, "relyingPartyRegistrationRepository cannot be null"); - this.matcher = new AntPathRequestMatcher(DEFAULT_FILTER_PROCESSES_URI); + Assert.hasText(filterProcessesUrl, "filterProcessesUrl must contain a URL pattern"); + Assert.isTrue( + filterProcessesUrl.contains("{registrationId}"), + "filterProcessesUrl must contain a {registrationId} match variable" + ); + this.matcher = new AntPathRequestMatcher(filterProcessesUrl); + setRequiresAuthenticationRequestMatcher(this.matcher); this.relyingPartyRegistrationRepository = relyingPartyRegistrationRepository; setAllowSessionCreation(true); setSessionAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy()); diff --git a/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java new file mode 100644 index 00000000000..2c9d5086c8c --- /dev/null +++ b/saml2/saml2-service-provider/src/test/java/org/springframework/security/saml2/provider/service/servlet/filter/Saml2WebSsoAuthenticationFilterTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2019 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.saml2.provider.service.servlet.filter; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository; + +import javax.servlet.http.HttpServletResponse; + +import static org.mockito.Mockito.mock; + +public class Saml2WebSsoAuthenticationFilterTests { + + private Saml2WebSsoAuthenticationFilter filter; + private RelyingPartyRegistrationRepository repository = mock(RelyingPartyRegistrationRepository.class); + private MockHttpServletRequest request = new MockHttpServletRequest(); + private HttpServletResponse response = new MockHttpServletResponse(); + + @Rule + public ExpectedException exception = ExpectedException.none(); + + @Before + public void setup() { + filter = new Saml2WebSsoAuthenticationFilter(repository); + request.setPathInfo("/login/saml2/sso/idp-registration-id"); + request.setParameter("SAMLResponse", "xml-data-goes-here"); + } + + @Test + public void constructingFilterWithMissingRegistrationIdVariableThenThrowsException() { + exception.expect(IllegalArgumentException.class); + exception.expectMessage("filterProcessesUrl must contain a {registrationId} match variable"); + filter = new Saml2WebSsoAuthenticationFilter(repository, "/url/missing/variable"); + } + + @Test + public void constructingFilterWithValidRegistrationIdVariableThenSucceeds() { + filter = new Saml2WebSsoAuthenticationFilter(repository, "/url/variable/is/present/{registrationId}"); + } + + @Test + public void requiresAuthenticationWhenHappyPathThenReturnsTrue() { + Assert.assertTrue(filter.requiresAuthentication(request, response)); + } + + @Test + public void requiresAuthenticationWhenCustomProcessingUrlThenReturnsTrue() { + filter = new Saml2WebSsoAuthenticationFilter(repository, "/some/other/path/{registrationId}"); + request.setPathInfo("/some/other/path/idp-registration-id"); + request.setParameter("SAMLResponse", "xml-data-goes-here"); + Assert.assertTrue(filter.requiresAuthentication(request, response)); + } + + +} diff --git a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java index 23abe5e4d67..1f40142987f 100644 --- a/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java +++ b/samples/boot/saml2login/src/integration-test/java/org/springframework/security/samples/Saml2LoginIntegrationTests.java @@ -51,7 +51,6 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.context.annotation.ComponentScan; import org.springframework.http.MediaType; import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException; import org.springframework.test.context.junit4.SpringRunner; @@ -111,7 +110,6 @@ public class Saml2LoginIntegrationTests { @SpringBootConfiguration @EnableAutoConfiguration - @ComponentScan(basePackages = "sample") public static class SpringBootApplicationTestConfig { } diff --git a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java index ea4f74a04a3..ccf4154e27c 100644 --- a/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java +++ b/samples/javaconfig/saml2login/src/main/java/org/springframework/security/samples/config/SecurityConfig.java @@ -76,6 +76,7 @@ protected void configure(HttpSecurity http) throws Exception { getSaml2AuthenticationConfiguration() ) ) + .loginProcessingUrl("/sample/jc/saml2/sso/{registrationId}") ; // @formatter:on } diff --git a/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java b/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java index d550276a9b1..3943eb91e08 100644 --- a/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java +++ b/samples/javaconfig/saml2login/src/test/java/org/springframework/security/samples/config/SecurityConfigTests.java @@ -15,17 +15,46 @@ */ package org.springframework.security.samples.config; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.security.saml2.provider.service.servlet.filter.Saml2WebSsoAuthenticationFilter; +import org.springframework.security.web.FilterChainProxy; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.util.ReflectionTestUtils; -import org.junit.Test; -import org.junit.runner.RunWith; +import java.util.List; +import javax.servlet.Filter; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SecurityConfig.class) public class SecurityConfigTests { + @Autowired + ApplicationContext context; + @Test public void securityConfigurationLoads() { } + + @Test + public void filterWhenLoginProcessingUrlIsSetInJavaConfigThenTheFilterHasIt() { + FilterChainProxy filterChain = context.getBean(FilterChainProxy.class); + Assert.assertNotNull(filterChain); + final List filters = filterChain.getFilters("/sample/jc/saml2/sso/test-id"); + Assert.assertNotNull(filters); + Saml2WebSsoAuthenticationFilter filter = (Saml2WebSsoAuthenticationFilter) filters + .stream() + .filter( + f -> f instanceof Saml2WebSsoAuthenticationFilter + ) + .findFirst() + .get(); + final Object matcher = ReflectionTestUtils.getField(filter, "requiresAuthenticationRequestMatcher"); + final Object pattern = ReflectionTestUtils.getField(matcher, "pattern"); + Assert.assertEquals("loginProcessingUrl mismatch", "/sample/jc/saml2/sso/{registrationId}", pattern); + } }