From d6d0d89ff865af87bfeaef4463cf216b28d92abd Mon Sep 17 00:00:00 2001 From: Josh Cummings Date: Mon, 2 Sep 2019 13:08:21 -0600 Subject: [PATCH] NamespaceRememberMeTests groovy->java Issue gh-4939 --- .../NamespaceRememberMeTests.groovy | 391 ------------- .../configurers/NamespaceRememberMeTests.java | 515 ++++++++++++++++++ 2 files changed, 515 insertions(+), 391 deletions(-) delete mode 100644 config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.groovy create mode 100644 config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.groovy deleted file mode 100644 index f6be3bffce..0000000000 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.groovy +++ /dev/null @@ -1,391 +0,0 @@ -/* - * Copyright 2002-2015 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.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.BaseWebConfig; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter -import org.springframework.security.web.authentication.logout.LogoutHandler; - -import javax.servlet.http.Cookie - -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration -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.AuthenticationManager -import org.springframework.security.authentication.RememberMeAuthenticationToken; -import org.springframework.security.config.annotation.BaseSpringSpec -import org.springframework.security.core.authority.AuthorityUtils; -import org.springframework.security.core.context.SecurityContext -import org.springframework.security.core.userdetails.UserDetailsService -import org.springframework.security.web.FilterChainProxy -import org.springframework.security.web.authentication.AuthenticationSuccessHandler -import org.springframework.security.web.authentication.RememberMeServices -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter -import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices -import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices; -import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; -import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter -import org.springframework.security.web.context.HttpRequestResponseHolder -import org.springframework.security.web.context.HttpSessionSecurityContextRepository -import org.springframework.test.util.ReflectionTestUtils; - -/** - * Tests to verify that all the functionality of attributes is present - * - * @author Rob Winch - * - */ -public class NamespaceRememberMeTests extends BaseSpringSpec { - - def "http/remember-me"() { - 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" - } - - @Configuration - static class RememberMeConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .authorizeRequests() - .anyRequest().hasRole("USER") - .and() - .formLogin() - .and() - .rememberMe() - } - } - - // See SEC-3170 - static interface RememberMeServicesLogoutHandler extends RememberMeServices, LogoutHandler{} - - def "http/remember-me@services-ref"() { - setup: - RememberMeServicesRefConfig.REMEMBER_ME_SERVICES = Mock(RememberMeServicesLogoutHandler) - when: "use custom remember-me services" - loadConfig(RememberMeServicesRefConfig) - then: "custom remember-me services used" - findFilter(RememberMeAuthenticationFilter).rememberMeServices == RememberMeServicesRefConfig.REMEMBER_ME_SERVICES - findFilter(UsernamePasswordAuthenticationFilter).rememberMeServices == RememberMeServicesRefConfig.REMEMBER_ME_SERVICES - } - - @Configuration - static class RememberMeServicesRefConfig extends BaseWebConfig { - static RememberMeServices REMEMBER_ME_SERVICES - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .rememberMeServices(REMEMBER_ME_SERVICES) - } - } - - def "http/remember-me@authentication-success-handler-ref"() { - setup: - AuthSuccessConfig.SUCCESS_HANDLER = Mock(AuthenticationSuccessHandler) - when: "use custom success handler" - loadConfig(AuthSuccessConfig) - then: "custom remember-me success handler is used" - findFilter(RememberMeAuthenticationFilter).successHandler == AuthSuccessConfig.SUCCESS_HANDLER - } - - @Configuration - static class AuthSuccessConfig extends BaseWebConfig { - static AuthenticationSuccessHandler SUCCESS_HANDLER - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .authenticationSuccessHandler(SUCCESS_HANDLER) - } - } - - // http/remember-me@data-source-ref is not supported directly. Instead use http/remember-me@token-repository-ref example - - def "http/remember-me@key"() { - when: "use custom key" - loadConfig(KeyConfig) - AuthenticationManager authManager = context.getBean(AuthenticationManager) - then: "custom key services used" - findFilter(RememberMeAuthenticationFilter).rememberMeServices.key == "KeyConfig" - authManager.authenticate(new RememberMeAuthenticationToken("KeyConfig", "user", AuthorityUtils.createAuthorityList("ROLE_USER"))) - } - - @Configuration - static class KeyConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .key("KeyConfig") - } - - @Bean - @Override - public AuthenticationManager authenticationManagerBean() - throws Exception { - return super.authenticationManagerBean(); - } - } - - // http/remember-me@services-alias is not supported use standard aliasing instead (i.e. @Bean("alias")) - - def "http/remember-me@token-repository-ref"() { - setup: - TokenRepositoryRefConfig.TOKEN_REPOSITORY = Mock(PersistentTokenRepository) - when: "use custom token services" - loadConfig(TokenRepositoryRefConfig) - then: "custom token services used with PersistentTokenBasedRememberMeServices" - PersistentTokenBasedRememberMeServices rememberMeServices = findFilter(RememberMeAuthenticationFilter).rememberMeServices - findFilter(RememberMeAuthenticationFilter).rememberMeServices.tokenRepository == TokenRepositoryRefConfig.TOKEN_REPOSITORY - } - - @Configuration - static class TokenRepositoryRefConfig extends BaseWebConfig { - static PersistentTokenRepository TOKEN_REPOSITORY - protected void configure(HttpSecurity http) throws Exception { - // JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl() - // tokenRepository.setDataSource(dataSource); - http - .formLogin() - .and() - .rememberMe() - .tokenRepository(TOKEN_REPOSITORY) - } - } - - def "http/remember-me@token-validity-seconds"() { - when: "use token validity" - loadConfig(TokenValiditySecondsConfig) - then: "custom token validity used" - findFilter(RememberMeAuthenticationFilter).rememberMeServices.tokenValiditySeconds == 1 - } - - @Configuration - static class TokenValiditySecondsConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .tokenValiditySeconds(1) - } - } - - def "http/remember-me@token-validity-seconds default"() { - when: "use token validity" - loadConfig(DefaultTokenValiditySecondsConfig) - then: "custom token validity used" - findFilter(RememberMeAuthenticationFilter).rememberMeServices.tokenValiditySeconds == AbstractRememberMeServices.TWO_WEEKS_S - } - - @Configuration - static class DefaultTokenValiditySecondsConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - } - } - - def "http/remember-me@use-secure-cookie"() { - when: "use secure cookies = true" - loadConfig(UseSecureCookieConfig) - then: "secure cookies will be used" - ReflectionTestUtils.getField(findFilter(RememberMeAuthenticationFilter).rememberMeServices, "useSecureCookie") == true - } - - @Configuration - static class UseSecureCookieConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .useSecureCookie(true) - } - } - - def "http/remember-me@remember-me-parameter"() { - when: "use custom rememberMeParameter" - loadConfig(RememberMeParameterConfig) - then: "custom rememberMeParameter will be used" - findFilter(RememberMeAuthenticationFilter).rememberMeServices.parameter == "rememberMe" - } - - @Configuration - static class RememberMeParameterConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .rememberMeParameter("rememberMe") - } - } - - // SEC-2880 - def "http/remember-me@remember-me-cookie"() { - when: "use custom rememberMeCookieName" - loadConfig(RememberMeCookieNameConfig) - then: "custom rememberMeCookieName will be used" - findFilter(RememberMeAuthenticationFilter).rememberMeServices.cookieName == "rememberMe" - } - - @Configuration - static class RememberMeCookieNameConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .rememberMeCookieName("rememberMe") - } - } - - def "http/remember-me@use-secure-cookie defaults"() { - when: "use secure cookies not specified" - loadConfig(DefaultUseSecureCookieConfig) - then: "secure cookies will be null (use secure if the request is secure)" - ReflectionTestUtils.getField(findFilter(RememberMeAuthenticationFilter).rememberMeServices, "useSecureCookie") == null - } - - @Configuration - static class DefaultUseSecureCookieConfig extends BaseWebConfig { - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - } - } - - def "http/remember-me defaults UserDetailsService with custom UserDetailsService"() { - setup: - DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE = Mock(UserDetailsService) - loadConfig(DefaultsUserDetailsServiceWithDaoConfig) - when: - request.setCookies(createRememberMeCookie()) - springSecurityFilterChain.doFilter(request, response, chain) - then: "RememberMeServices defaults to the custom UserDetailsService" - 1 * DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE.loadUserByUsername("user") - } - - @EnableWebSecurity - static class DefaultsUserDetailsServiceWithDaoConfig extends WebSecurityConfigurerAdapter { - static UserDetailsService USERDETAILS_SERVICE - - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - } - - protected void configure(AuthenticationManagerBuilder auth) throws Exception { - auth - .userDetailsService(USERDETAILS_SERVICE); - } - } - - def "http/remember-me@user-service-ref"() { - setup: - UserServiceRefConfig.USERDETAILS_SERVICE = Mock(UserDetailsService) - when: "use custom UserDetailsService" - loadConfig(UserServiceRefConfig) - then: "custom UserDetailsService is used" - ReflectionTestUtils.getField(findFilter(RememberMeAuthenticationFilter).rememberMeServices, "userDetailsService") == UserServiceRefConfig.USERDETAILS_SERVICE - } - - @Configuration - static class UserServiceRefConfig extends BaseWebConfig { - static UserDetailsService USERDETAILS_SERVICE - protected void configure(HttpSecurity http) throws Exception { - http - .formLogin() - .and() - .rememberMe() - .userDetailsService(USERDETAILS_SERVICE) - } - } - - 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/NamespaceRememberMeTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java new file mode 100644 index 0000000000..d42cf46110 --- /dev/null +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceRememberMeTests.java @@ -0,0 +1,515 @@ +/* + * 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 javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.junit.Rule; +import org.junit.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.mock.web.MockHttpSession; +import org.springframework.security.authentication.RememberMeAuthenticationToken; +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.Authentication; +import org.springframework.security.core.authority.AuthorityUtils; +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.AuthenticationSuccessHandler; +import org.springframework.security.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices; +import org.springframework.security.web.authentication.rememberme.PersistentRememberMeToken; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.request.RequestPostProcessor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; +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; + +/** + * Tests to verify that all the functionality of attributes is present + * + * @author Rob Winch + * @author Josh Cummings + * + */ +public class NamespaceRememberMeTests { + + @Rule + public final SpringTestRule spring = new SpringTestRule(); + + @Autowired + MockMvc mvc; + + @Test + public void rememberMeLoginWhenUsingDefaultsThenMatchesNamespace() throws Exception { + this.spring.register(RememberMeConfig.class, SecurityController.class).autowire(); + MvcResult result = this.mvc.perform(post("/login") + .with(rememberMeLogin())) + .andReturn(); + + MockHttpSession session = (MockHttpSession) result.getRequest().getSession(); + Cookie rememberMe = result.getResponse().getCookie("remember-me"); + assertThat(rememberMe).isNotNull(); + this.mvc.perform(get("/authentication-class") + .cookie(rememberMe)) + .andExpect(content().string(RememberMeAuthenticationToken.class.getName())); + + result = this.mvc.perform(post("/logout").with(csrf()) + .session(session) + .cookie(rememberMe)) + .andExpect(redirectedUrl("/login?logout")) + .andReturn(); + + rememberMe = result.getResponse().getCookie("remember-me"); + assertThat(rememberMe).isNotNull().extracting("maxAge").containsExactly(0); + + this.mvc.perform(post("/authentication-class").with(csrf()) + .cookie(rememberMe)) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn(); + } + + @Configuration + @EnableWebSecurity + static class RememberMeConfig extends UsersConfig { + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().hasRole("USER") + .and() + .formLogin() + .and() + .rememberMe(); + // @formatter:on + } + } + + // SEC-3170 - RememberMeService implementations should not have to also implement LogoutHandler + @Test + public void logoutWhenCustomRememberMeServicesDeclaredThenUses() throws Exception { + RememberMeServicesRefConfig.REMEMBER_ME_SERVICES = mock(RememberMeServicesWithoutLogoutHandler.class); + this.spring.register(RememberMeServicesRefConfig.class).autowire(); + + this.mvc.perform(get("/")); + verify(RememberMeServicesRefConfig.REMEMBER_ME_SERVICES) + .autoLogin(any(HttpServletRequest.class), any(HttpServletResponse.class)); + + this.mvc.perform(post("/login").with(csrf())); + verify(RememberMeServicesRefConfig.REMEMBER_ME_SERVICES) + .loginFail(any(HttpServletRequest.class), any(HttpServletResponse.class)); + } + + interface RememberMeServicesWithoutLogoutHandler extends RememberMeServices {} + + @Configuration + @EnableWebSecurity + static class RememberMeServicesRefConfig extends WebSecurityConfigurerAdapter { + static RememberMeServices REMEMBER_ME_SERVICES; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin() + .and() + .rememberMe() + .rememberMeServices(REMEMBER_ME_SERVICES); + // @formatter:on + } + } + + @Test + public void rememberMeLoginWhenAuthenticationSuccessHandlerDeclaredThenUses() throws Exception { + AuthSuccessConfig.SUCCESS_HANDLER = mock(AuthenticationSuccessHandler.class); + this.spring.register(AuthSuccessConfig.class).autowire(); + + MvcResult result = this.mvc.perform(post("/login") + .with(rememberMeLogin())) + .andReturn(); + + verifyZeroInteractions(AuthSuccessConfig.SUCCESS_HANDLER); + + Cookie rememberMe = result.getResponse().getCookie("remember-me"); + assertThat(rememberMe).isNotNull(); + this.mvc.perform(get("/somewhere") + .cookie(rememberMe)); + + verify(AuthSuccessConfig.SUCCESS_HANDLER).onAuthenticationSuccess + (any(HttpServletRequest.class), any(HttpServletResponse.class), any(Authentication.class)); + } + + @Configuration + @EnableWebSecurity + static class AuthSuccessConfig extends UsersConfig { + static AuthenticationSuccessHandler SUCCESS_HANDLER; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin() + .and() + .rememberMe() + .authenticationSuccessHandler(SUCCESS_HANDLER); + // @formatter:on + } + } + + @Test + public void rememberMeLoginWhenKeyDeclaredThenMatchesNamespace() throws Exception { + this.spring.register(WithoutKeyConfig.class, KeyConfig.class, SecurityController.class).autowire(); + Cookie withoutKey = this.mvc.perform(post("/without-key/login") + .with(rememberMeLogin())) + .andExpect(redirectedUrl("/")) + .andReturn().getResponse().getCookie("remember-me"); + + this.mvc.perform(get("/somewhere") + .cookie(withoutKey)) + .andExpect(status().isFound()) + .andExpect(redirectedUrl("http://localhost/login")); + + Cookie withKey = this.mvc.perform(post("/login") + .with(rememberMeLogin())) + .andReturn().getResponse().getCookie("remember-me"); + this.mvc.perform(get("/somewhere") + .cookie(withKey)) + .andExpect(status().isNotFound()); + } + + @Configuration + @EnableWebSecurity + @Order(0) + static class WithoutKeyConfig extends UsersConfig { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .antMatcher("/without-key/**") + .formLogin() + .loginProcessingUrl("/without-key/login") + .and() + .rememberMe(); + // @formatter:on + } + } + + @Configuration + @EnableWebSecurity + static class KeyConfig extends UsersConfig { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin() + .and() + .rememberMe() + .key("KeyConfig"); + // @formatter:on + } + } + + // http/remember-me@services-alias is not supported use standard aliasing instead (i.e. @Bean("alias")) + + // http/remember-me@data-source-ref is not supported directly. Instead use http/remember-me@token-repository-ref example + @Test + public void rememberMeLoginWhenDeclaredTokenRepositoryThenMatchesNamespace() throws Exception { + TokenRepositoryRefConfig.TOKEN_REPOSITORY = mock(PersistentTokenRepository.class); + this.spring.register(TokenRepositoryRefConfig.class).autowire(); + + this.mvc.perform(post("/login") + .with(rememberMeLogin())); + + verify(TokenRepositoryRefConfig.TOKEN_REPOSITORY).createNewToken(any(PersistentRememberMeToken.class)); + } + + @Configuration + @EnableWebSecurity + static class TokenRepositoryRefConfig extends UsersConfig { + static PersistentTokenRepository TOKEN_REPOSITORY; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl() + // tokenRepository.setDataSource(dataSource); + + // @formatter:off + http + .formLogin() + .and() + .rememberMe() + .tokenRepository(TOKEN_REPOSITORY); + // @formatter:on + } + } + + @Test + public void rememberMeLoginWhenTokenValidityDeclaredThenMatchesNamespace() throws Exception { + this.spring.register(TokenValiditySecondsConfig.class).autowire(); + Cookie expiredRememberMe = this.mvc.perform(post("/login") + .with(rememberMeLogin())) + .andReturn().getResponse().getCookie("remember-me"); + + assertThat(expiredRememberMe).extracting("maxAge").containsExactly(314); + } + + @Configuration + @EnableWebSecurity + static class TokenValiditySecondsConfig extends UsersConfig { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeRequests() + .anyRequest().authenticated() + .and() + .formLogin() + .and() + .rememberMe() + .tokenValiditySeconds(314); + // @formatter:on + } + } + + @Test + public void rememberMeLoginWhenUsingDefaultsThenCookieMaxAgeMatchesNamespace() throws Exception { + this.spring.register(RememberMeConfig.class).autowire(); + Cookie expiredRememberMe = this.mvc.perform(post("/login") + .with(rememberMeLogin())) + .andReturn().getResponse().getCookie("remember-me"); + + assertThat(expiredRememberMe).extracting("maxAge") + .containsExactly(AbstractRememberMeServices.TWO_WEEKS_S); + } + + @Test + public void rememberMeLoginWhenUsingSecureCookieThenMatchesNamespace() throws Exception { + this.spring.register(UseSecureCookieConfig.class).autowire(); + Cookie secureCookie = this.mvc.perform(post("/login") + .with(rememberMeLogin())) + .andReturn().getResponse().getCookie("remember-me"); + + assertThat(secureCookie).extracting("secure").containsExactly(true); + } + + @Configuration + @EnableWebSecurity + static class UseSecureCookieConfig extends UsersConfig { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin() + .and() + .rememberMe() + .useSecureCookie(true); + // @formatter:on + } + } + + @Test + public void rememberMeLoginWhenUsingDefaultsThenCookieSecurityMatchesNamespace() throws Exception { + this.spring.register(RememberMeConfig.class).autowire(); + Cookie secureCookie = this.mvc.perform(post("/login") + .with(rememberMeLogin()) + .secure(true)) + .andReturn().getResponse().getCookie("remember-me"); + + assertThat(secureCookie).extracting("secure").containsExactly(true); + } + + @Test + public void rememberMeLoginWhenParameterSpecifiedThenMatchesNamespace() throws Exception { + this.spring.register(RememberMeParameterConfig.class).autowire(); + Cookie rememberMe = this.mvc.perform(post("/login") + .with(rememberMeLogin("rememberMe", true))) + .andReturn().getResponse().getCookie("remember-me"); + + assertThat(rememberMe).isNotNull(); + } + + @Configuration + @EnableWebSecurity + static class RememberMeParameterConfig extends UsersConfig { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin() + .and() + .rememberMe() + .rememberMeParameter("rememberMe"); + // @formatter:on + } + } + + // SEC-2880 + + @Test + public void rememberMeLoginWhenCookieNameDeclaredThenMatchesNamespace() throws Exception { + this.spring.register(RememberMeCookieNameConfig.class).autowire(); + Cookie rememberMe = this.mvc.perform(post("/login") + .with(rememberMeLogin())) + .andReturn().getResponse().getCookie("rememberMe"); + + assertThat(rememberMe).isNotNull(); + } + + @Configuration + @EnableWebSecurity + static class RememberMeCookieNameConfig extends UsersConfig { + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin() + .and() + .rememberMe() + .rememberMeCookieName("rememberMe"); + // @formatter:on + } + } + + @Test + public void rememberMeLoginWhenGlobalUserDetailsServiceDeclaredThenMatchesNamespace() throws Exception { + DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE = mock(UserDetailsService.class); + this.spring.register(DefaultsUserDetailsServiceWithDaoConfig.class).autowire(); + + this.mvc.perform(post("/login") + .with(rememberMeLogin())); + + verify(DefaultsUserDetailsServiceWithDaoConfig.USERDETAILS_SERVICE) + .loadUserByUsername("user"); + } + + @EnableWebSecurity + @Configuration + static class DefaultsUserDetailsServiceWithDaoConfig extends WebSecurityConfigurerAdapter { + static UserDetailsService USERDETAILS_SERVICE; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin() + .and() + .rememberMe(); + // @formatter:on + } + + @Override + protected void configure(AuthenticationManagerBuilder auth) throws Exception { + auth + .userDetailsService(USERDETAILS_SERVICE); + } + } + + @Test + public void rememberMeLoginWhenUserDetailsServiceDeclaredThenMatchesNamespace() throws Exception { + UserServiceRefConfig.USERDETAILS_SERVICE = mock(UserDetailsService.class); + this.spring.register(UserServiceRefConfig.class).autowire(); + + when(UserServiceRefConfig.USERDETAILS_SERVICE.loadUserByUsername("user")) + .thenReturn(new User("user", "password", AuthorityUtils.createAuthorityList("ROLE_USER"))); + + this.mvc.perform(post("/login") + .with(rememberMeLogin())); + + verify(UserServiceRefConfig.USERDETAILS_SERVICE) + .loadUserByUsername("user"); + } + + @Configuration + @EnableWebSecurity + static class UserServiceRefConfig extends UsersConfig { + static UserDetailsService USERDETAILS_SERVICE; + + @Override + protected void configure(HttpSecurity http) throws Exception { + // @formatter:off + http + .formLogin() + .and() + .rememberMe() + .userDetailsService(USERDETAILS_SERVICE); + // @formatter:on + } + } + + static RequestPostProcessor rememberMeLogin() { + return rememberMeLogin("remember-me", true); + } + + static RequestPostProcessor rememberMeLogin(String parameterName, boolean parameterValue) { + return request -> { + csrf().postProcessRequest(request); + request.setParameter("username", "user"); + request.setParameter("password", "password"); + request.setParameter(parameterName, String.valueOf(parameterValue)); + return request; + }; + } + + static class UsersConfig extends WebSecurityConfigurerAdapter { + @Override + @Bean + public UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager( + User.withDefaultPasswordEncoder() + .username("user") + .password("password") + .roles("USER") + .build()); + } + } + + @RestController + static class SecurityController { + @GetMapping("/authentication-class") + String authenticationClass(Authentication authentication) { + return authentication.getClass().getName(); + } + } +}