|
17 | 17 | package org.springframework.security.config.annotation.web.configurers;
|
18 | 18 |
|
19 | 19 | import java.net.URI;
|
| 20 | +import java.util.Arrays; |
| 21 | +import java.util.List; |
20 | 22 |
|
| 23 | +import javax.servlet.http.Cookie; |
21 | 24 | import javax.servlet.http.HttpServletRequest;
|
22 | 25 | import javax.servlet.http.HttpServletResponse;
|
23 | 26 |
|
|
28 | 31 | import org.springframework.beans.factory.annotation.Autowired;
|
29 | 32 | import org.springframework.context.annotation.Bean;
|
30 | 33 | import org.springframework.context.annotation.Configuration;
|
| 34 | +import org.springframework.http.HttpHeaders; |
31 | 35 | import org.springframework.http.HttpMethod;
|
32 | 36 | import org.springframework.mock.web.MockHttpSession;
|
33 | 37 | import org.springframework.security.config.Customizer;
|
|
42 | 46 | import org.springframework.security.web.SecurityFilterChain;
|
43 | 47 | import org.springframework.security.web.access.AccessDeniedHandler;
|
44 | 48 | import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
|
| 49 | +import org.springframework.security.web.csrf.CookieCsrfTokenRepository; |
45 | 50 | import org.springframework.security.web.csrf.CsrfToken;
|
46 | 51 | import org.springframework.security.web.csrf.CsrfTokenRepository;
|
47 | 52 | import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
|
@@ -509,6 +514,86 @@ public void loginWhenXorCsrfTokenRequestAttributeHandlerSetAndMaskedCsrfTokenThe
|
509 | 514 | verifyNoMoreInteractions(csrfTokenRepository);
|
510 | 515 | }
|
511 | 516 |
|
| 517 | + @Test |
| 518 | + public void loginWhenFormLoginAndCookieCsrfTokenRepositorySetAndExistingTokenThenRemovesAndGeneratesNewToken() |
| 519 | + throws Exception { |
| 520 | + CsrfToken csrfToken = new DefaultCsrfToken("X-XSRF-TOKEN", "_csrf", "token"); |
| 521 | + Cookie existingCookie = new Cookie("XSRF-TOKEN", csrfToken.getToken()); |
| 522 | + CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); |
| 523 | + csrfTokenRepository.setCookieName(existingCookie.getName()); |
| 524 | + CsrfTokenRequestHandlerConfig.REPO = csrfTokenRepository; |
| 525 | + CsrfTokenRequestHandlerConfig.HANDLER = new CsrfTokenRequestAttributeHandler(); |
| 526 | + this.spring.register(CsrfTokenRequestHandlerConfig.class, BasicController.class).autowire(); |
| 527 | + |
| 528 | + // @formatter:off |
| 529 | + MockHttpServletRequestBuilder loginRequest = post("/login") |
| 530 | + .cookie(existingCookie) |
| 531 | + .header(csrfToken.getHeaderName(), csrfToken.getToken()) |
| 532 | + .param("username", "user") |
| 533 | + .param("password", "password"); |
| 534 | + // @formatter:on |
| 535 | + MvcResult mvcResult = this.mvc.perform(loginRequest).andExpect(redirectedUrl("/")).andReturn(); |
| 536 | + List<Cookie> cookies = Arrays.asList(mvcResult.getResponse().getCookies()); |
| 537 | + cookies.removeIf((cookie) -> !cookie.getName().equalsIgnoreCase(existingCookie.getName())); |
| 538 | + assertThat(cookies).hasSize(2); |
| 539 | + assertThat(cookies.get(0).getValue()).isEmpty(); |
| 540 | + assertThat(cookies.get(1).getValue()).isNotEmpty(); |
| 541 | + } |
| 542 | + |
| 543 | + @Test |
| 544 | + public void postWhenHttpBasicAndCookieCsrfTokenRepositorySetAndExistingTokenThenRemovesAndGeneratesNewToken() |
| 545 | + throws Exception { |
| 546 | + CsrfToken csrfToken = new DefaultCsrfToken("X-XSRF-TOKEN", "_csrf", "token"); |
| 547 | + Cookie existingCookie = new Cookie("XSRF-TOKEN", csrfToken.getToken()); |
| 548 | + CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); |
| 549 | + csrfTokenRepository.setCookieName(existingCookie.getName()); |
| 550 | + HttpBasicCsrfTokenRequestHandlerConfig.REPO = csrfTokenRepository; |
| 551 | + HttpBasicCsrfTokenRequestHandlerConfig.HANDLER = new CsrfTokenRequestAttributeHandler(); |
| 552 | + this.spring.register(HttpBasicCsrfTokenRequestHandlerConfig.class, BasicController.class).autowire(); |
| 553 | + |
| 554 | + HttpHeaders headers = new HttpHeaders(); |
| 555 | + headers.set(csrfToken.getHeaderName(), csrfToken.getToken()); |
| 556 | + headers.setBasicAuth("user", "password"); |
| 557 | + // @formatter:off |
| 558 | + MvcResult mvcResult = this.mvc.perform(post("/") |
| 559 | + .cookie(existingCookie) |
| 560 | + .headers(headers)) |
| 561 | + .andExpect(status().isOk()) |
| 562 | + .andReturn(); |
| 563 | + // @formatter:on |
| 564 | + List<Cookie> cookies = Arrays.asList(mvcResult.getResponse().getCookies()); |
| 565 | + cookies.removeIf((cookie) -> !cookie.getName().equalsIgnoreCase(existingCookie.getName())); |
| 566 | + assertThat(cookies).hasSize(2); |
| 567 | + assertThat(cookies.get(0).getValue()).isEmpty(); |
| 568 | + assertThat(cookies.get(1).getValue()).isNotEmpty(); |
| 569 | + } |
| 570 | + |
| 571 | + @Test |
| 572 | + public void getWhenHttpBasicAndCookieCsrfTokenRepositorySetAndNoExistingCookieThenGeneratesNewToken() |
| 573 | + throws Exception { |
| 574 | + CsrfToken csrfToken = new DefaultCsrfToken("X-XSRF-TOKEN", "_csrf", "token"); |
| 575 | + Cookie expectedCookie = new Cookie("XSRF-TOKEN", csrfToken.getToken()); |
| 576 | + CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse(); |
| 577 | + csrfTokenRepository.setCookieName(expectedCookie.getName()); |
| 578 | + HttpBasicCsrfTokenRequestHandlerConfig.REPO = csrfTokenRepository; |
| 579 | + HttpBasicCsrfTokenRequestHandlerConfig.HANDLER = new CsrfTokenRequestAttributeHandler(); |
| 580 | + this.spring.register(HttpBasicCsrfTokenRequestHandlerConfig.class, BasicController.class).autowire(); |
| 581 | + |
| 582 | + HttpHeaders headers = new HttpHeaders(); |
| 583 | + headers.set(csrfToken.getHeaderName(), csrfToken.getToken()); |
| 584 | + headers.setBasicAuth("user", "password"); |
| 585 | + // @formatter:off |
| 586 | + MvcResult mvcResult = this.mvc.perform(get("/") |
| 587 | + .headers(headers)) |
| 588 | + .andExpect(status().isOk()) |
| 589 | + .andReturn(); |
| 590 | + // @formatter:on |
| 591 | + List<Cookie> cookies = Arrays.asList(mvcResult.getResponse().getCookies()); |
| 592 | + cookies.removeIf((cookie) -> !cookie.getName().equalsIgnoreCase(expectedCookie.getName())); |
| 593 | + assertThat(cookies).hasSize(1); |
| 594 | + assertThat(cookies.get(0).getValue()).isNotEmpty(); |
| 595 | + } |
| 596 | + |
512 | 597 | @Configuration
|
513 | 598 | static class AllowHttpMethodsFirewallConfig {
|
514 | 599 |
|
@@ -886,6 +971,42 @@ void configure(AuthenticationManagerBuilder auth) throws Exception {
|
886 | 971 |
|
887 | 972 | }
|
888 | 973 |
|
| 974 | + @Configuration |
| 975 | + @EnableWebSecurity |
| 976 | + static class HttpBasicCsrfTokenRequestHandlerConfig { |
| 977 | + |
| 978 | + static CsrfTokenRepository REPO; |
| 979 | + |
| 980 | + static CsrfTokenRequestHandler HANDLER; |
| 981 | + |
| 982 | + @Bean |
| 983 | + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { |
| 984 | + // @formatter:off |
| 985 | + http |
| 986 | + .authorizeHttpRequests((authorize) -> authorize |
| 987 | + .anyRequest().authenticated() |
| 988 | + ) |
| 989 | + .httpBasic(Customizer.withDefaults()) |
| 990 | + .csrf((csrf) -> csrf |
| 991 | + .csrfTokenRepository(REPO) |
| 992 | + .csrfTokenRequestHandler(HANDLER) |
| 993 | + ); |
| 994 | + // @formatter:on |
| 995 | + |
| 996 | + return http.build(); |
| 997 | + } |
| 998 | + |
| 999 | + @Autowired |
| 1000 | + void configure(AuthenticationManagerBuilder auth) throws Exception { |
| 1001 | + // @formatter:off |
| 1002 | + auth |
| 1003 | + .inMemoryAuthentication() |
| 1004 | + .withUser(PasswordEncodedUser.user()); |
| 1005 | + // @formatter:on |
| 1006 | + } |
| 1007 | + |
| 1008 | + } |
| 1009 | + |
889 | 1010 | @RestController
|
890 | 1011 | static class BasicController {
|
891 | 1012 |
|
|
0 commit comments