From abe7da6b8526d89157f39bd4fafe20eee27f9f3e Mon Sep 17 00:00:00 2001 From: Eleftheria Stein Date: Tue, 4 Jun 2019 14:41:23 -0400 Subject: [PATCH] Migrate RememberMeConfigurerTests groovy->java Issue: gh-4939 --- .../RememberMeConfigurerTests.groovy | 290 -------------- .../RememberMeConfigurerTests.java | 376 ++++++++++++++++++ 2 files changed, 376 insertions(+), 290 deletions(-) delete mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy deleted file mode 100644 index 1993d3b398..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.groovy +++ /dev/null @@ -1,290 +0,0 @@ -/* - * Copyright 2002-2016 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.config.annotation.web.configurers - -import org.springframework.security.core.userdetails.PasswordEncodedUser - -import javax.servlet.http.Cookie - -import org.springframework.beans.factory.BeanCreationException -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.mock.web.MockFilterChain -import org.springframework.mock.web.MockHttpServletRequest -import org.springframework.mock.web.MockHttpServletResponse -import org.springframework.mock.web.MockHttpSession -import org.springframework.security.authentication.RememberMeAuthenticationToken -import org.springframework.security.authentication.dao.DaoAuthenticationProvider -import org.springframework.security.config.annotation.AnyObjectPostProcessor -import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.config.annotation.ObjectPostProcessor -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder -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 -import org.springframework.security.core.authority.AuthorityUtils -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.userdetails.User -import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.security.provisioning.InMemoryUserDetailsManager -import org.springframework.security.web.authentication.RememberMeServices -import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter -import org.springframework.security.web.context.HttpRequestResponseHolder -import org.springframework.security.web.context.HttpSessionSecurityContextRepository - -/** - * Tests for RememberMeConfigurer that flex edge cases. {@link NamespaceRememberMeTests} demonstrate mapping of the XML namespace to Java Config. - * - * @author Rob Winch - * @author Eddú Meléndez - */ -public class RememberMeConfigurerTests extends BaseSpringSpec { - - def "rememberMe() null UserDetailsService provides meaningful error"() { - setup: "Load Config without UserDetailsService specified" - loadConfig(NullUserDetailsConfig) - when: - request.setCookies(createRememberMeCookie()) - springSecurityFilterChain.doFilter(request, response, chain) - then: "A good error message is provided" - Exception success = thrown() - success.message.contains "UserDetailsService is required" - } - - @EnableWebSecurity - static class NullUserDetailsConfig extends WebSecurityConfigurerAdapter { - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .formLogin() - .and() - .rememberMe() - } - - @Override - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - User user = PasswordEncodedUser.user(); - DaoAuthenticationProvider provider = new DaoAuthenticationProvider() - provider.userDetailsService = new InMemoryUserDetailsManager([user]) - auth - .authenticationProvider(provider) - } - } - - def "rememberMe ObjectPostProcessor"() { - setup: - AnyObjectPostProcessor opp = Mock() - HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) - UserDetailsService uds = authenticationBldr.getDefaultUserDetailsService() - when: - http - .rememberMe() - .userDetailsService(authenticationBldr.getDefaultUserDetailsService()) - .and() - .build() - - then: "RememberMeAuthenticationFilter is registered with LifecycleManager" - 1 * opp.postProcess(_ as RememberMeAuthenticationFilter) >> {RememberMeAuthenticationFilter o -> o} - } - - def "invoke rememberMe twice does not reset"() { - setup: - AnyObjectPostProcessor opp = Mock() - HttpSecurity http = new HttpSecurity(opp, authenticationBldr, [:]) - UserDetailsService uds = authenticationBldr.getDefaultUserDetailsService() - when: - http - .rememberMe() - .userDetailsService(authenticationBldr.getDefaultUserDetailsService()) - .and() - .rememberMe() - then: "RememberMeAuthenticationFilter is registered with LifecycleManager" - http.getConfigurer(RememberMeConfigurer).userDetailsService != null - } - - - def "http/remember-me with Global AuthenticationManagerBuilder"() { - setup: - loadConfig(RememberMeConfig) - when: "login with remember me" - super.setup() - request.servletPath = "/login" - request.method = "POST" - request.parameters.username = ["user"] as String[] - request.parameters.password = ["password"] as String[] - request.parameters.'remember-me' = ["true"] as String[] - springSecurityFilterChain.doFilter(request,response,chain) - Cookie rememberMeCookie = getRememberMeCookie() - then: "response contains remember me cookie" - rememberMeCookie != null - when: "session expires" - super.setup() - request.setCookies(rememberMeCookie) - request.requestURI = "/abc" - springSecurityFilterChain.doFilter(request,response,chain) - MockHttpSession session = request.getSession() - then: "initialized to RememberMeAuthenticationToken" - SecurityContext context = new HttpSessionSecurityContextRepository().loadContext(new HttpRequestResponseHolder(request, response)) - context.getAuthentication() instanceof RememberMeAuthenticationToken - when: "logout" - super.setup() - request.setSession(session) - super.setupCsrf() - request.setCookies(rememberMeCookie) - request.servletPath = "/logout" - request.method = "POST" - springSecurityFilterChain.doFilter(request,response,chain) - rememberMeCookie = getRememberMeCookie() - then: "logout cookie expired" - response.getRedirectedUrl() == "/login?logout" - rememberMeCookie.maxAge == 0 - when: "use remember me after logout" - super.setup() - request.setCookies(rememberMeCookie) - request.requestURI = "/abc" - springSecurityFilterChain.doFilter(request,response,chain) - then: "sent to default login page" - response.getRedirectedUrl() == "http://localhost/login" - } - - def "http/remember-me with cookie domain"() { - setup: - loadConfig(RememberMeCookieDomainConfig) - when: - super.setup() - request.servletPath = "/login" - request.method = "POST" - request.parameters.username = ["user"] as String[] - request.parameters.password = ["password"] as String[] - request.parameters.'remember-me' = ["true"] as String[] - springSecurityFilterChain.doFilter(request,response,chain) - Cookie rememberMeCookie = getRememberMeCookie() - then: "response contains remember me cookie" - rememberMeCookie != null - rememberMeCookie.domain == "spring.io" - } - - def "http/remember-me with cookie name and custom rememberMeServices throws BeanCreationException"() { - setup: - RememberMeCookieDomainCustomRememberMeServicesConfig.REMEMBER_ME = Mock(RememberMeServices) - when: - loadConfig(RememberMeCookieDomainCustomRememberMeServicesConfig) - then: "response contains remember me cookie" - def ex = thrown(BeanCreationException) - ex instanceof BeanCreationException - } - - def "http/remember-me with cookie name and custom rememberMeServices throws IllegalArgumentException"() { - setup: - def httpSec = new HttpSecurity(Mock(ObjectPostProcessor), Mock(AuthenticationManagerBuilder), [:]) - RememberMeConfigurer config = new RememberMeConfigurer(); - config.rememberMeCookieName("COOKIE_NAME") - config.rememberMeServices(Mock(RememberMeServices)) - when: - config.init(httpSec) - then: - IllegalArgumentException ex = thrown() - ex.message == 'Can not set rememberMeCookieName and custom rememberMeServices.' - } - - @EnableWebSecurity - static class RememberMeConfig extends WebSecurityConfigurerAdapter { - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .formLogin() - .and() - .rememberMe() - } - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) { - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()); - } - } - - @EnableWebSecurity - static class RememberMeCookieDomainConfig extends WebSecurityConfigurerAdapter { - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .formLogin() - .and() - .rememberMe() - .rememberMeCookieDomain("spring.io") - } - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) { - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()); - } - } - - @EnableWebSecurity - static class RememberMeCookieDomainCustomRememberMeServicesConfig extends - WebSecurityConfigurerAdapter { - static RememberMeServices REMEMBER_ME - - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .formLogin() - .and() - .rememberMe() - .rememberMeCookieName("SPRING_COOKIE_DOMAIN") - .rememberMeCookieDomain("spring.io") - .rememberMeServices(REMEMBER_ME); - } - - @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) { - auth - .inMemoryAuthentication() - .withUser(PasswordEncodedUser.user()); - } - - } - - Cookie createRememberMeCookie() { - MockHttpServletRequest request = new MockHttpServletRequest("GET", "") - MockHttpServletResponse response = new MockHttpServletResponse() - super.setupCsrf("CSRF_TOKEN", request, response) - - MockFilterChain chain = new MockFilterChain() - request.servletPath = "/login" - request.method = "POST" - request.parameters.username = ["user"] as String[] - request.parameters.password = ["password"] as String[] - request.parameters.'remember-me' = ["true"] as String[] - springSecurityFilterChain.doFilter(request, response, chain) - response.getCookie("remember-me") - } - - Cookie getRememberMeCookie(String cookieName="remember-me") { - response.getCookie(cookieName) - } -} diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java new file mode 100644 index 0000000000..4c217ee2d9 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RememberMeConfigurerTests.java @@ -0,0 +1,376 @@ +/* + * 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.config.annotation.web.configurers; + +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.annotation.Bean; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.authentication.RememberMeAuthenticationToken; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.ObjectPostProcessor; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +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; +import org.springframework.security.config.test.SpringTestRule; +import org.springframework.security.core.userdetails.PasswordEncodedUser; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.provisioning.InMemoryUserDetailsManager; +import org.springframework.security.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; + +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpSession; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic; +import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated; +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.cookie; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; + +/** + * Tests for {@link RememberMeConfigurer} + * + * @author Rob Winch + * @author Eddú Meléndez + * @author Eleftheria Stein + */ +public class RememberMeConfigurerTests { + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Test + public void postWhenNoUserDetailsServiceThenException() throws Exception { + this.spring.register(NullUserDetailsConfig.class).autowire(); + + assertThatThrownBy(() -> + mvc.perform(post("/login") + .param("username", "user") + .param("password", "password") + .param("remember-me", "true") + .with(csrf()))) + .hasMessageContaining("UserDetailsService is required"); + } + + @EnableWebSecurity + static class NullUserDetailsConfig extends WebSecurityConfigurerAdapter { + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .formLogin() + .and() + .rememberMe(); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + User user = (User) PasswordEncodedUser.user(); + DaoAuthenticationProvider provider = new DaoAuthenticationProvider(); + provider.setUserDetailsService(new InMemoryUserDetailsManager(Collections.singletonList(user))); + // @formatter:off + auth + .authenticationProvider(provider); + // @formatter:on + } + } + + @Test + public void configureWhenRegisteringObjectPostProcessorThenInvokedOnRememberMeAuthenticationFilter() { + this.spring.register(ObjectPostProcessorConfig.class).autowire(); + + verify(ObjectPostProcessorConfig.objectPostProcessor) + .postProcess(any(RememberMeAuthenticationFilter.class)); + } + + @EnableWebSecurity + static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter { + static ObjectPostProcessor objectPostProcessor = spy(ReflectingObjectPostProcessor.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .rememberMe() + .userDetailsService(new AuthenticationManagerBuilder(objectPostProcessor).getDefaultUserDetailsService()); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication(); + // @formatter:on + } + + @Bean + static ObjectPostProcessor objectPostProcessor() { + return objectPostProcessor; + } + } + + static class ReflectingObjectPostProcessor implements ObjectPostProcessor { + @Override + public O postProcess(O object) { + return object; + } + } + + @Test + public void rememberMeWhenInvokedTwiceThenUsesOriginalUserDetailsService() throws Exception { + when(DuplicateDoesNotOverrideConfig.userDetailsService.loadUserByUsername(anyString())) + .thenReturn(new User("user", "password", Collections.emptyList())); + this.spring.register(DuplicateDoesNotOverrideConfig.class).autowire(); + + this.mvc.perform(get("/") + .with(httpBasic("user", "password")) + .param("remember-me", "true")); + + verify(DuplicateDoesNotOverrideConfig.userDetailsService).loadUserByUsername("user"); + } + + @EnableWebSecurity + static class DuplicateDoesNotOverrideConfig extends WebSecurityConfigurerAdapter { + static UserDetailsService userDetailsService = mock(UserDetailsService.class); + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .httpBasic() + .and() + .rememberMe() + .userDetailsService(userDetailsService) + .and() + .rememberMe(); + // @formatter:on + } + + @Bean + public UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager( + User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("USER") + .build() + ); + } + } + + @Test + public void loginWhenRememberMeTrueThenRespondsWithRememberMeCookie() throws Exception { + this.spring.register(RememberMeConfig.class).autowire(); + + this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true")) + .andExpect(cookie().exists("remember-me")); + } + + @Test + public void getWhenRememberMeCookieThenAuthenticationIsRememberMeAuthenticationToken() throws Exception { + this.spring.register(RememberMeConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true")) + .andReturn(); + Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); + + this.mvc.perform(get("/abc") + .cookie(rememberMeCookie)) + .andExpect(authenticated().withAuthentication(auth -> + assertThat(auth).isInstanceOf(RememberMeAuthenticationToken.class))); + } + + @Test + public void logoutWhenRememberMeCookieThenAuthenticationIsRememberMeCookieExpired() throws Exception { + this.spring.register(RememberMeConfig.class).autowire(); + + MvcResult mvcResult = this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true")) + .andReturn(); + Cookie rememberMeCookie = mvcResult.getResponse().getCookie("remember-me"); + HttpSession session = mvcResult.getRequest().getSession(); + + this.mvc.perform(post("/logout") + .with(csrf()) + .cookie(rememberMeCookie) + .session((MockHttpSession) session)) + .andExpect(redirectedUrl("/login?logout")) + .andExpect(cookie().maxAge("remember-me", 0)); + } + + @Test + public void getWhenRememberMeCookieAndLoggedOutThenRedirectsToLogin() throws Exception { + this.spring.register(RememberMeConfig.class).autowire(); + + MvcResult loginMvcResult = this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true")) + .andReturn(); + Cookie rememberMeCookie = loginMvcResult.getResponse().getCookie("remember-me"); + HttpSession session = loginMvcResult.getRequest().getSession(); + MvcResult logoutMvcResult = this.mvc.perform(post("/logout") + .with(csrf()) + .cookie(rememberMeCookie) + .session((MockHttpSession) session)) + .andReturn(); + Cookie expiredRememberMeCookie = logoutMvcResult.getResponse().getCookie("remember-me"); + + this.mvc.perform(get("/abc") + .with(csrf()) + .cookie(expiredRememberMeCookie)) + .andExpect(redirectedUrl("http://localhost/login")); + } + + @EnableWebSecurity + static class RememberMeConfig extends WebSecurityConfigurerAdapter { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .formLogin() + .and() + .rememberMe(); + // @formatter:on + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @Test + public void loginWhenRememberMeTrueAndCookieDomainThenRememberMeCookieHasDomain() throws Exception { + this.spring.register(RememberMeCookieDomainConfig.class).autowire(); + + this.mvc.perform(post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true")) + .andExpect(cookie().exists("remember-me")) + .andExpect(cookie().domain("remember-me", "spring.io")); + } + + @EnableWebSecurity + static class RememberMeCookieDomainConfig extends WebSecurityConfigurerAdapter { + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .formLogin() + .and() + .rememberMe() + .rememberMeCookieDomain("spring.io"); + // @formatter:on + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } + + @Test + public void configureWhenRememberMeCookieNameAndRememberMeServicesThenException() { + assertThatThrownBy(() -> this.spring.register(RememberMeCookieNameAndRememberMeServicesConfig.class).autowire()) + .isInstanceOf(BeanCreationException.class) + .hasRootCauseInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("Can not set rememberMeCookieName and custom rememberMeServices."); + } + + @EnableWebSecurity + static class RememberMeCookieNameAndRememberMeServicesConfig extends WebSecurityConfigurerAdapter { + static RememberMeServices REMEMBER_ME = mock(RememberMeServices.class); + + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .formLogin() + .and() + .rememberMe() + .rememberMeCookieName("SPRING_COOKIE_DOMAIN") + .rememberMeCookieDomain("spring.io") + .rememberMeServices(REMEMBER_ME); + // @formatter:on + } + + @Autowired + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + // @formatter:off + auth + .inMemoryAuthentication() + .withUser(PasswordEncodedUser.user()); + // @formatter:on + } + } +}