Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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;
}
Expand Down