|
|
|
@ -1,5 +1,5 @@ |
|
|
|
/* |
|
|
|
/* |
|
|
|
* Copyright 2002-2022 the original author or authors. |
|
|
|
* Copyright 2002-2023 the original author or authors. |
|
|
|
* |
|
|
|
* |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
* you may not use this file except in compliance with the License. |
|
|
|
@ -16,6 +16,8 @@ |
|
|
|
|
|
|
|
|
|
|
|
package org.springframework.security.config.annotation.web.configurers; |
|
|
|
package org.springframework.security.config.annotation.web.configurers; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import jakarta.servlet.http.HttpServletRequest; |
|
|
|
|
|
|
|
import jakarta.servlet.http.HttpServletResponse; |
|
|
|
import org.apache.http.HttpHeaders; |
|
|
|
import org.apache.http.HttpHeaders; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.junit.jupiter.api.Test; |
|
|
|
import org.junit.jupiter.api.extension.ExtendWith; |
|
|
|
import org.junit.jupiter.api.extension.ExtendWith; |
|
|
|
@ -25,6 +27,8 @@ import org.springframework.beans.factory.annotation.Autowired; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Bean; |
|
|
|
import org.springframework.context.annotation.Configuration; |
|
|
|
import org.springframework.context.annotation.Configuration; |
|
|
|
import org.springframework.http.MediaType; |
|
|
|
import org.springframework.http.MediaType; |
|
|
|
|
|
|
|
import org.springframework.mock.web.MockHttpSession; |
|
|
|
|
|
|
|
import org.springframework.security.config.Customizer; |
|
|
|
import org.springframework.security.config.annotation.ObjectPostProcessor; |
|
|
|
import org.springframework.security.config.annotation.ObjectPostProcessor; |
|
|
|
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; |
|
|
|
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig; |
|
|
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
|
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity; |
|
|
|
@ -39,6 +43,8 @@ import org.springframework.security.web.SecurityFilterChain; |
|
|
|
import org.springframework.security.web.authentication.RememberMeServices; |
|
|
|
import org.springframework.security.web.authentication.RememberMeServices; |
|
|
|
import org.springframework.security.web.authentication.logout.LogoutFilter; |
|
|
|
import org.springframework.security.web.authentication.logout.LogoutFilter; |
|
|
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; |
|
|
|
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; |
|
|
|
|
|
|
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository; |
|
|
|
|
|
|
|
import org.springframework.security.web.context.SecurityContextRepository; |
|
|
|
import org.springframework.security.web.util.matcher.RequestMatcher; |
|
|
|
import org.springframework.security.web.util.matcher.RequestMatcher; |
|
|
|
import org.springframework.test.web.servlet.MockMvc; |
|
|
|
import org.springframework.test.web.servlet.MockMvc; |
|
|
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; |
|
|
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; |
|
|
|
@ -48,6 +54,7 @@ import static org.mockito.ArgumentMatchers.any; |
|
|
|
import static org.mockito.Mockito.atLeastOnce; |
|
|
|
import static org.mockito.Mockito.atLeastOnce; |
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
import static org.mockito.Mockito.mock; |
|
|
|
import static org.mockito.Mockito.spy; |
|
|
|
import static org.mockito.Mockito.spy; |
|
|
|
|
|
|
|
import static org.mockito.Mockito.times; |
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
import static org.mockito.Mockito.verify; |
|
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; |
|
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; |
|
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; |
|
|
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; |
|
|
|
@ -324,6 +331,80 @@ public class LogoutConfigurerTests { |
|
|
|
this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isNotFound()); |
|
|
|
this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isNotFound()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
public void logoutWhenCustomSecurityContextRepositoryThenUses() throws Exception { |
|
|
|
|
|
|
|
CustomSecurityContextRepositoryConfig.repository = mock(SecurityContextRepository.class); |
|
|
|
|
|
|
|
this.spring.register(CustomSecurityContextRepositoryConfig.class).autowire(); |
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
|
|
|
|
MockHttpServletRequestBuilder logoutRequest = post("/logout") |
|
|
|
|
|
|
|
.with(csrf()) |
|
|
|
|
|
|
|
.with(user("user")) |
|
|
|
|
|
|
|
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); |
|
|
|
|
|
|
|
this.mvc.perform(logoutRequest) |
|
|
|
|
|
|
|
.andExpect(status().isFound()) |
|
|
|
|
|
|
|
.andExpect(redirectedUrl("/login?logout")); |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
int invocationCount = 2; // 1 from user() post processor and 1 from
|
|
|
|
|
|
|
|
// SecurityContextLogoutHandler
|
|
|
|
|
|
|
|
verify(CustomSecurityContextRepositoryConfig.repository, times(invocationCount)).saveContext(any(), |
|
|
|
|
|
|
|
any(HttpServletRequest.class), any(HttpServletResponse.class)); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Test |
|
|
|
|
|
|
|
public void logoutWhenNoSecurityContextRepositoryThenHttpSessionSecurityContextRepository() throws Exception { |
|
|
|
|
|
|
|
this.spring.register(InvalidateHttpSessionFalseConfig.class).autowire(); |
|
|
|
|
|
|
|
MockHttpSession session = mock(MockHttpSession.class); |
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
|
|
|
|
MockHttpServletRequestBuilder logoutRequest = post("/logout") |
|
|
|
|
|
|
|
.with(csrf()) |
|
|
|
|
|
|
|
.with(user("user")) |
|
|
|
|
|
|
|
.session(session) |
|
|
|
|
|
|
|
.header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); |
|
|
|
|
|
|
|
this.mvc.perform(logoutRequest) |
|
|
|
|
|
|
|
.andExpect(status().isFound()) |
|
|
|
|
|
|
|
.andExpect(redirectedUrl("/login?logout")) |
|
|
|
|
|
|
|
.andReturn(); |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
verify(session).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Configuration |
|
|
|
|
|
|
|
@EnableWebSecurity |
|
|
|
|
|
|
|
static class InvalidateHttpSessionFalseConfig { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
|
|
|
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
|
|
|
|
http |
|
|
|
|
|
|
|
.logout((logout) -> logout.invalidateHttpSession(false)) |
|
|
|
|
|
|
|
.securityContext((context) -> context.requireExplicitSave(true)); |
|
|
|
|
|
|
|
return http.build(); |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Configuration |
|
|
|
|
|
|
|
@EnableWebSecurity |
|
|
|
|
|
|
|
static class CustomSecurityContextRepositoryConfig { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static SecurityContextRepository repository; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Bean |
|
|
|
|
|
|
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
|
|
|
|
|
|
|
// @formatter:off
|
|
|
|
|
|
|
|
http |
|
|
|
|
|
|
|
.logout(Customizer.withDefaults()) |
|
|
|
|
|
|
|
.securityContext((context) -> context |
|
|
|
|
|
|
|
.requireExplicitSave(true) |
|
|
|
|
|
|
|
.securityContextRepository(repository) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
return http.build(); |
|
|
|
|
|
|
|
// @formatter:on
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Configuration |
|
|
|
@Configuration |
|
|
|
@EnableWebSecurity |
|
|
|
@EnableWebSecurity |
|
|
|
static class NullLogoutSuccessHandlerConfig { |
|
|
|
static class NullLogoutSuccessHandlerConfig { |
|
|
|
|