|
1 | 1 | /*
|
2 |
| - * Copyright 2002-2022 the original author or authors. |
| 2 | + * Copyright 2002-2023 the original author or authors. |
3 | 3 | *
|
4 | 4 | * Licensed under the Apache License, Version 2.0 (the "License");
|
5 | 5 | * you may not use this file except in compliance with the License.
|
|
16 | 16 |
|
17 | 17 | package org.springframework.security.config.annotation.web.configurers;
|
18 | 18 |
|
| 19 | +import jakarta.servlet.http.HttpServletRequest; |
| 20 | +import jakarta.servlet.http.HttpServletResponse; |
19 | 21 | import org.apache.http.HttpHeaders;
|
20 | 22 | import org.junit.jupiter.api.Test;
|
21 | 23 | import org.junit.jupiter.api.extension.ExtendWith;
|
|
25 | 27 | import org.springframework.context.annotation.Bean;
|
26 | 28 | import org.springframework.context.annotation.Configuration;
|
27 | 29 | import org.springframework.http.MediaType;
|
| 30 | +import org.springframework.mock.web.MockHttpSession; |
| 31 | +import org.springframework.security.config.Customizer; |
28 | 32 | import org.springframework.security.config.annotation.ObjectPostProcessor;
|
29 | 33 | import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
30 | 34 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
39 | 43 | import org.springframework.security.web.authentication.RememberMeServices;
|
40 | 44 | import org.springframework.security.web.authentication.logout.LogoutFilter;
|
41 | 45 | import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
|
| 46 | +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; |
| 47 | +import org.springframework.security.web.context.SecurityContextRepository; |
42 | 48 | import org.springframework.security.web.util.matcher.RequestMatcher;
|
43 | 49 | import org.springframework.test.web.servlet.MockMvc;
|
44 | 50 | import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
|
48 | 54 | import static org.mockito.Mockito.atLeastOnce;
|
49 | 55 | import static org.mockito.Mockito.mock;
|
50 | 56 | import static org.mockito.Mockito.spy;
|
| 57 | +import static org.mockito.Mockito.times; |
51 | 58 | import static org.mockito.Mockito.verify;
|
52 | 59 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
53 | 60 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
@@ -324,6 +331,80 @@ public void logoutWhenDisabledThenLogoutUrlNotFound() throws Exception {
|
324 | 331 | this.mvc.perform(post("/logout").with(csrf())).andExpect(status().isNotFound());
|
325 | 332 | }
|
326 | 333 |
|
| 334 | + @Test |
| 335 | + public void logoutWhenCustomSecurityContextRepositoryThenUses() throws Exception { |
| 336 | + CustomSecurityContextRepositoryConfig.repository = mock(SecurityContextRepository.class); |
| 337 | + this.spring.register(CustomSecurityContextRepositoryConfig.class).autowire(); |
| 338 | + // @formatter:off |
| 339 | + MockHttpServletRequestBuilder logoutRequest = post("/logout") |
| 340 | + .with(csrf()) |
| 341 | + .with(user("user")) |
| 342 | + .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); |
| 343 | + this.mvc.perform(logoutRequest) |
| 344 | + .andExpect(status().isFound()) |
| 345 | + .andExpect(redirectedUrl("/login?logout")); |
| 346 | + // @formatter:on |
| 347 | + int invocationCount = 2; // 1 from user() post processor and 1 from |
| 348 | + // SecurityContextLogoutHandler |
| 349 | + verify(CustomSecurityContextRepositoryConfig.repository, times(invocationCount)).saveContext(any(), |
| 350 | + any(HttpServletRequest.class), any(HttpServletResponse.class)); |
| 351 | + } |
| 352 | + |
| 353 | + @Test |
| 354 | + public void logoutWhenNoSecurityContextRepositoryThenHttpSessionSecurityContextRepository() throws Exception { |
| 355 | + this.spring.register(InvalidateHttpSessionFalseConfig.class).autowire(); |
| 356 | + MockHttpSession session = mock(MockHttpSession.class); |
| 357 | + // @formatter:off |
| 358 | + MockHttpServletRequestBuilder logoutRequest = post("/logout") |
| 359 | + .with(csrf()) |
| 360 | + .with(user("user")) |
| 361 | + .session(session) |
| 362 | + .header(HttpHeaders.ACCEPT, MediaType.TEXT_HTML_VALUE); |
| 363 | + this.mvc.perform(logoutRequest) |
| 364 | + .andExpect(status().isFound()) |
| 365 | + .andExpect(redirectedUrl("/login?logout")) |
| 366 | + .andReturn(); |
| 367 | + // @formatter:on |
| 368 | + verify(session).removeAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY); |
| 369 | + } |
| 370 | + |
| 371 | + @Configuration |
| 372 | + @EnableWebSecurity |
| 373 | + static class InvalidateHttpSessionFalseConfig { |
| 374 | + |
| 375 | + @Bean |
| 376 | + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
| 377 | + // @formatter:off |
| 378 | + http |
| 379 | + .logout((logout) -> logout.invalidateHttpSession(false)) |
| 380 | + .securityContext((context) -> context.requireExplicitSave(true)); |
| 381 | + return http.build(); |
| 382 | + // @formatter:on |
| 383 | + } |
| 384 | + |
| 385 | + } |
| 386 | + |
| 387 | + @Configuration |
| 388 | + @EnableWebSecurity |
| 389 | + static class CustomSecurityContextRepositoryConfig { |
| 390 | + |
| 391 | + static SecurityContextRepository repository; |
| 392 | + |
| 393 | + @Bean |
| 394 | + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { |
| 395 | + // @formatter:off |
| 396 | + http |
| 397 | + .logout(Customizer.withDefaults()) |
| 398 | + .securityContext((context) -> context |
| 399 | + .requireExplicitSave(true) |
| 400 | + .securityContextRepository(repository) |
| 401 | + ); |
| 402 | + return http.build(); |
| 403 | + // @formatter:on |
| 404 | + } |
| 405 | + |
| 406 | + } |
| 407 | + |
327 | 408 | @Configuration
|
328 | 409 | @EnableWebSecurity
|
329 | 410 | static class NullLogoutSuccessHandlerConfig {
|
|
0 commit comments