diff --git a/config/src/test/groovy/org/springframework/security/config/http/FormLoginConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/FormLoginConfigTests.groovy deleted file mode 100644 index 0697c8e290..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/http/FormLoginConfigTests.groovy +++ /dev/null @@ -1,161 +0,0 @@ -package org.springframework.security.config.http - -import javax.servlet.http.HttpServletResponse - -import org.springframework.beans.factory.BeanCreationException -import org.springframework.mock.web.MockFilterChain -import org.springframework.mock.web.MockHttpServletRequest -import org.springframework.mock.web.MockHttpServletResponse -import org.springframework.security.util.FieldUtils -import org.springframework.security.web.access.ExceptionTranslationFilter -import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter -import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter - -import spock.lang.Unroll; - -/** - * - * @author Luke Taylor - */ -class FormLoginConfigTests extends AbstractHttpConfigTests { - - def formLoginWithNoLoginPageAddsDefaultLoginPageFilter() { - httpAutoConfig('ant') { - form-login() - } - createAppContext() - filtersMatchExpectedAutoConfigList(); - } - - def 'Form login alwaysUseDefaultTarget sets correct property'() { - xml.http { - 'form-login'('default-target-url':'/default', 'always-use-default-target': 'true') - } - createAppContext() - def filter = getFilter(UsernamePasswordAuthenticationFilter.class); - - expect: - FieldUtils.getFieldValue(filter, 'successHandler.defaultTargetUrl') == '/default'; - FieldUtils.getFieldValue(filter, 'successHandler.alwaysUseDefaultTargetUrl'); - } - - def 'form-login attributes support SpEL'() { - setup: - def spelUrl = '#{T(org.springframework.security.config.http.WebConfigUtilsTest).URL}' - def expectedUrl = WebConfigUtilsTest.URL - when: - xml.http { - 'form-login'('default-target-url': spelUrl , 'authentication-failure-url': spelUrl, 'login-page': spelUrl) - } - createAppContext() - def unPwdFilter = getFilter(UsernamePasswordAuthenticationFilter) - def exTransFilter = getFilter(ExceptionTranslationFilter) - - then: - unPwdFilter.successHandler.defaultTargetUrl == expectedUrl - unPwdFilter - FieldUtils.getFieldValue(unPwdFilter, 'successHandler.defaultTargetUrl') == expectedUrl - FieldUtils.getFieldValue(unPwdFilter, 'failureHandler.defaultFailureUrl') == expectedUrl - FieldUtils.getFieldValue(exTransFilter, 'authenticationEntryPoint.loginFormUrl') == expectedUrl - } - - def invalidLoginPageIsDetected() { - when: - xml.http { - 'form-login'('login-page': 'noLeadingSlash') - } - createAppContext() - - then: - BeanCreationException e = thrown(); - } - - def invalidDefaultTargetUrlIsDetected() { - when: - xml.http { - 'form-login'('default-target-url': 'noLeadingSlash') - } - createAppContext() - - then: - BeanCreationException e = thrown(); - } - - def customSuccessAndFailureHandlersCanBeSetThroughTheNamespace() { - xml.http { - 'form-login'('authentication-success-handler-ref': 'sh', 'authentication-failure-handler-ref':'fh') - } - bean('sh', SavedRequestAwareAuthenticationSuccessHandler.class.name) - bean('fh', SimpleUrlAuthenticationFailureHandler.class.name) - createAppContext() - - def apf = getFilter(UsernamePasswordAuthenticationFilter.class); - - expect: - FieldUtils.getFieldValue(apf, "successHandler") == appContext.getBean("sh"); - FieldUtils.getFieldValue(apf, "failureHandler") == appContext.getBean("fh") - } - - def usernameAndPasswordParametersCanBeSetThroughNamespace() { - xml.http { - 'form-login'('username-parameter': 'xname', 'password-parameter':'xpass') - } - createAppContext() - - def apf = getFilter(UsernamePasswordAuthenticationFilter.class); - - expect: - apf.usernameParameter == 'xname'; - apf.passwordParameter == 'xpass' - } - - def 'SEC-2919: DefaultLoginGeneratingFilter should not be present if login-page="/login"'() { - when: - xml.http() { - 'form-login'('login-page':'/login') - } - createAppContext() - - then: - getFilter(DefaultLoginPageGeneratingFilter) == null - } - - @Unroll - def 'Form Login requires CSRF Token #csrfDisabled'(int status, boolean csrfDisabled) { - setup: - MockHttpServletRequest request = new MockHttpServletRequest(method:'POST',servletPath:'/login') - request.setParameter('username','user') - request.setParameter('password','password') - MockHttpServletResponse response = new MockHttpServletResponse() - MockFilterChain chain = new MockFilterChain() - httpAutoConfig { - 'form-login'() - csrf(disabled:csrfDisabled) {} - } - createAppContext() - when: - springSecurityFilterChain.doFilter(request,response,chain) - then: - response.status == status - where: - status | csrfDisabled - HttpServletResponse.SC_FORBIDDEN | false - HttpServletResponse.SC_MOVED_TEMPORARILY | true - } - - def 'SEC-3147: authentication-failure-url should be contained "error" parameter if login-page="/login"'() { - xml.http { - 'form-login'('login-page':'/login') - } - createAppContext() - - def apf = getFilter(UsernamePasswordAuthenticationFilter.class); - - expect: - apf.failureHandler.defaultFailureUrl == '/login?error' - } - - -} diff --git a/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java new file mode 100644 index 0000000000..00a0528f21 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/http/FormLoginConfigTests.java @@ -0,0 +1,262 @@ +/* + * Copyright 2002-2018 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 + * + * http://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.config.http; + +import org.junit.Rule; +import org.junit.Test; +import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.http.HttpStatus; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.Filter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +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; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * + * @author Luke Taylor + * @author Josh Cummings + */ +public class FormLoginConfigTests { + private static final String CONFIG_LOCATION_PREFIX = + "classpath:org/springframework/security/config/http/FormLoginConfigTests"; + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Test + public void getProtectedPageWhenFormLoginConfiguredThenRedirectsToDefaultLoginPage() + throws Exception { + + this.spring.configLocations(this.xml("WithAntRequestMatcher")).autowire(); + + this.mvc.perform(get("/")) + .andExpect(redirectedUrl("http://localhost/login")); + } + + @Test + public void authenticateWhenDefaultTargetUrlConfiguredThenRedirectsAccordingly() + throws Exception { + + this.spring.configLocations(this.xml("WithDefaultTargetUrl")).autowire(); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "password") + .with(csrf())) + .andExpect(redirectedUrl("/default")); + } + + @Test + public void authenticateWhenConfiguredWithSpelThenRedirectsAccordingly() + throws Exception { + + this.spring.configLocations(this.xml("UsingSpel")).autowire(); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "password") + .with(csrf())) + .andExpect(redirectedUrl(WebConfigUtilsTest.URL + "/default")); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "wrong") + .with(csrf())) + .andExpect(redirectedUrl(WebConfigUtilsTest.URL + "/failure")); + + this.mvc.perform(get("/")) + .andExpect(redirectedUrl("http://localhost" + WebConfigUtilsTest.URL + "/login")); + } + + @Test + public void autowireWhenLoginPageIsMisconfiguredThenDetects() { + + assertThatThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashLoginPage")).autowire()) + .isInstanceOf(BeanCreationException.class); + } + + @Test + public void autowireWhenDefaultTargetUrlIsMisconfiguredThenDetects() { + + assertThatThrownBy(() -> this.spring.configLocations(this.xml("NoLeadingSlashDefaultTargetUrl")).autowire()) + .isInstanceOf(BeanCreationException.class); + } + + @Test + public void authenticateWhenCustomHandlerBeansConfiguredThenInvokesAccordingly() + throws Exception { + + this.spring.configLocations(this.xml("WithSuccessAndFailureHandlers")).autowire(); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "password") + .with(csrf())) + .andExpect(status().isIAmATeapot()); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "wrong") + .with(csrf())) + .andExpect(status().isIAmATeapot()); + } + + + @Test + public void authenticateWhenCustomUsernameAndPasswordParametersThenSucceeds() + throws Exception { + + this.spring.configLocations(this.xml("WithUsernameAndPasswordParameters")).autowire(); + + this.mvc.perform(post("/login") + .param("xname", "user") + .param("xpass", "password") + .with(csrf())) + .andExpect(redirectedUrl("/")); + } + + /** + * SEC-2919 - DefaultLoginGeneratingFilter incorrectly used if login-url="/login" + */ + @Test + public void autowireWhenCustomLoginPageIsSlashLoginThenNoDefaultLoginPageGeneratingFilterIsWired() + throws Exception { + + this.spring.configLocations(this.xml("ForSec2919")).autowire(); + + this.mvc.perform(get("/login")) + .andExpect(content().string("teapot")); + + assertThat(getFilter(this.spring.getContext(), DefaultLoginPageGeneratingFilter.class)).isNull(); + } + + @Test + public void authenticateWhenCsrfIsEnabledThenRequiresToken() + throws Exception { + + this.spring.configLocations(this.xml("WithCsrfEnabled")).autowire(); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "password")) + .andExpect(status().isForbidden()); + } + + @Test + public void authenticateWhenCsrfIsDisabledThenDoesNotRequireToken() + throws Exception { + + this.spring.configLocations(this.xml("WithCsrfDisabled")).autowire(); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "password")) + .andExpect(status().isFound()); + } + + /** + * SEC-3147: authentication-failure-url should be contained "error" parameter if login-page="/login" + */ + @Test + public void authenticateWhenLoginPageIsSlashLoginAndAuthenticationFailsThenRedirectContainsErrorParameter() + throws Exception { + + this.spring.configLocations(this.xml("ForSec3147")).autowire(); + + this.mvc.perform(post("/login") + .param("username", "user") + .param("password", "wrong") + .with(csrf())) + .andExpect(redirectedUrl("/login?error")); + } + + @RestController + public static class LoginController { + @GetMapping("/login") + public String ok() { + return "teapot"; + } + } + + public static class TeapotAuthenticationHandler implements + AuthenticationSuccessHandler, + AuthenticationFailureHandler { + + @Override + public void onAuthenticationFailure( + HttpServletRequest request, + HttpServletResponse response, + AuthenticationException exception) throws IOException, ServletException { + + response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); + } + + @Override + public void onAuthenticationSuccess( + HttpServletRequest request, + HttpServletResponse response, + Authentication authentication) throws IOException, ServletException { + + response.setStatus(HttpStatus.I_AM_A_TEAPOT.value()); + } + } + + private Filter getFilter(ApplicationContext context, Class filterClass) { + FilterChainProxy filterChain = context.getBean(BeanIds.FILTER_CHAIN_PROXY, FilterChainProxy.class); + + List filters = filterChain.getFilters("/any"); + + for ( Filter filter : filters ) { + if ( filter.getClass() == filterClass ) { + return filter; + } + } + + return null; + } + + private String xml(String configName) { + return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml"; + } +} diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-ForSec2919.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-ForSec2919.xml new file mode 100644 index 0000000000..f937d463db --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-ForSec2919.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-ForSec3147.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-ForSec3147.xml new file mode 100644 index 0000000000..f937d463db --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-ForSec3147.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-NoLeadingSlashDefaultTargetUrl.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-NoLeadingSlashDefaultTargetUrl.xml new file mode 100644 index 0000000000..7dd209ca8c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-NoLeadingSlashDefaultTargetUrl.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-NoLeadingSlashLoginPage.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-NoLeadingSlashLoginPage.xml new file mode 100644 index 0000000000..e45afc297f --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-NoLeadingSlashLoginPage.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-UsingSpel.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-UsingSpel.xml new file mode 100644 index 0000000000..2a17a20e93 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-UsingSpel.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithAntRequestMatcher.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithAntRequestMatcher.xml new file mode 100644 index 0000000000..0c447baf5c --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithAntRequestMatcher.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCsrfDisabled.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCsrfDisabled.xml new file mode 100644 index 0000000000..7bc74f9f04 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCsrfDisabled.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCsrfEnabled.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCsrfEnabled.xml new file mode 100644 index 0000000000..ed85359c66 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithCsrfEnabled.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithDefaultTargetUrl.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithDefaultTargetUrl.xml new file mode 100644 index 0000000000..2db5e28a5a --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithDefaultTargetUrl.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithSuccessAndFailureHandlers.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithSuccessAndFailureHandlers.xml new file mode 100644 index 0000000000..8b92fb7150 --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithSuccessAndFailureHandlers.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + diff --git a/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithUsernameAndPasswordParameters.xml b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithUsernameAndPasswordParameters.xml new file mode 100644 index 0000000000..7dfc7044ad --- /dev/null +++ b/config/src/test/resources/org/springframework/security/config/http/FormLoginConfigTests-WithUsernameAndPasswordParameters.xml @@ -0,0 +1,32 @@ + + + + + + + + + + +