| 
					
				 | 
			
			
				@@ -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"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  * you may not use this file except in compliance with the License. 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -16,6 +16,9 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 package org.springframework.security.config.annotation.web.configurers; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import javax.servlet.http.HttpServletRequest; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import javax.servlet.http.HttpServletResponse; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.apache.http.HttpHeaders; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.junit.jupiter.api.Test; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.junit.jupiter.api.extension.ExtendWith; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -23,7 +26,10 @@ import org.junit.jupiter.api.extension.ExtendWith; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.beans.factory.BeanCreationException; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.beans.factory.annotation.Autowired; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.context.annotation.Bean; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.context.annotation.Configuration; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 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.SecurityContextChangedListenerConfig; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -33,9 +39,12 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.config.test.SpringTestContext; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.config.test.SpringTestContextExtension; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.core.context.SecurityContextHolderStrategy; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import org.springframework.security.web.SecurityFilterChain; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.web.authentication.RememberMeServices; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.security.web.authentication.logout.LogoutFilter; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 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.test.web.servlet.MockMvc; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -45,6 +54,7 @@ import static org.mockito.ArgumentMatchers.any; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import static org.mockito.Mockito.atLeastOnce; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import static org.mockito.Mockito.mock; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import static org.mockito.Mockito.spy; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import static org.mockito.Mockito.times; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 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.user; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -321,6 +331,80 @@ public class LogoutConfigurerTests { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 		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 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+		} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+	} 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	@EnableWebSecurity 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 	static class NullLogoutSuccessHandlerConfig extends WebSecurityConfigurerAdapter { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 |