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 index 034fd18fbb2..606c581c6e1 100644 --- 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 @@ -49,6 +49,8 @@ import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.RememberMeServices; +import org.springframework.security.web.authentication.rememberme.InMemoryTokenRepositoryImpl; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.TokenBasedRememberMeServices; import org.springframework.security.web.context.HttpRequestResponseHolder; @@ -73,6 +75,7 @@ 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.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated; 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; @@ -214,6 +217,32 @@ public void logoutWhenRememberMeCookieThenAuthenticationIsRememberMeCookieExpire // @formatter:on } + // gh-10241 + @Test + public void logoutWhenSessionIsInvalidThenRememberMeTokenIsInvalidated() throws Exception { + this.spring.register(PersistentRememberMeConfig.class).autowire(); + + MockHttpServletRequestBuilder loginRequest = post("/login") + .with(csrf()) + .param("username", "user") + .param("password", "password") + .param("remember-me", "true"); + + MvcResult loginMvcResult = this.mvc.perform(loginRequest).andReturn(); + Cookie rememberMeCookie = loginMvcResult.getResponse().getCookie("remember-me"); + + MockHttpServletRequestBuilder logoutRequest = post("/logout") + .with(csrf()) + .cookie(rememberMeCookie); + + this.mvc.perform(logoutRequest) + .andExpect(redirectedUrl("/login?logout")) + .andExpect(cookie().maxAge("remember-me", 0)); + + this.mvc.perform(get("/").cookie(rememberMeCookie)) + .andExpect(unauthenticated()); + } + @Test public void getWhenRememberMeCookieAndLoggedOutThenRedirectsToLogin() throws Exception { this.spring.register(RememberMeConfig.class).autowire(); @@ -500,6 +529,37 @@ UserDetailsService userDetailsService() { } + @Configuration + @EnableWebSecurity + static class PersistentRememberMeConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http, PersistentTokenRepository tokenRepository) throws Exception { + // @formatter:off + http + .authorizeHttpRequests((requests) -> requests + .anyRequest().hasRole("USER") + ) + .formLogin(withDefaults()) + .rememberMe((rememberMe) -> rememberMe + .tokenRepository(tokenRepository) + ); + return http.build(); + // @formatter:on + } + + @Bean + PersistentTokenRepository persistentTokenRepository() { + return new InMemoryTokenRepositoryImpl(); + } + + @Bean + UserDetailsService userDetailsService() { + return new InMemoryUserDetailsManager(PasswordEncodedUser.user()); + } + + } + @Configuration @EnableWebSecurity static class RememberMeInLambdaConfig { diff --git a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java index e6359451ff7..6519cdbb7dd 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java +++ b/web/src/main/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServices.java @@ -162,6 +162,21 @@ public void logout(HttpServletRequest request, HttpServletResponse response, super.logout(request, response, authentication); if (authentication != null) { this.tokenRepository.removeUserTokens(authentication.getName()); + return; + } + + String rememberMeCookie = extractRememberMeCookie(request); + if (rememberMeCookie != null) { + try { + String[] cookieTokens = decodeCookie(rememberMeCookie); + PersistentRememberMeToken persistentToken = this.tokenRepository.getTokenForSeries(cookieTokens[0]); + if (persistentToken != null) { + this.tokenRepository.removeUserTokens(persistentToken.getUsername()); + } + } + catch (InvalidCookieException ex) { + // Ignore invalid remember-me cookies during logout + } } } diff --git a/web/src/test/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServicesTests.java b/web/src/test/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServicesTests.java index e5587dee3aa..9a7e9509384 100644 --- a/web/src/test/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServicesTests.java +++ b/web/src/test/java/org/springframework/security/web/authentication/rememberme/PersistentTokenBasedRememberMeServicesTests.java @@ -130,6 +130,18 @@ public void logoutClearsUsersTokenAndCookie() { this.services.logout(request, response, null); } + + @Test + public void logoutWhenAuthenticationIsNullAndRememberMeCookieIsPresentThenClearsUsersToken() { + this.services = create(new PersistentRememberMeToken("joe", "series", "token", new Date())); + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setCookies(new Cookie("mycookiename", this.services.encodeCookie(new String[] { "series", "token" }))); + + this.services.logout(request, new MockHttpServletResponse(), null); + + assertThat(this.repo.getRemovedUsername()).isEqualTo("joe"); + } + private PersistentTokenBasedRememberMeServices create(PersistentRememberMeToken token) { this.repo = new MockTokenRepository(token); PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices("key", @@ -165,8 +177,15 @@ public PersistentRememberMeToken getTokenForSeries(String seriesId) { @Override public void removeUserTokens(String username) { + this.removedUsername = username; } + + private String removedUsername; + + String getRemovedUsername() { + return this.removedUsername; + } PersistentRememberMeToken getStoredToken() { return this.storedToken; }