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 jakarta .servlet .http .Cookie ;
21
24
import jakarta .servlet .http .HttpServletRequest ;
22
25
import jakarta .servlet .http .HttpServletResponse ;
23
26
import org .junit .jupiter .api .Test ;
27
30
import org .springframework .beans .factory .annotation .Autowired ;
28
31
import org .springframework .context .annotation .Bean ;
29
32
import org .springframework .context .annotation .Configuration ;
33
+ import org .springframework .http .HttpHeaders ;
30
34
import org .springframework .http .HttpMethod ;
31
35
import org .springframework .mock .web .MockHttpSession ;
32
36
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 ;
@@ -308,6 +313,7 @@ public void logoutWhenCustomCsrfTokenRepositoryThenCsrfTokenIsCleared() throws E
308
313
public void loginWhenCustomCsrfTokenRepositoryThenCsrfTokenIsCleared () throws Exception {
309
314
CsrfTokenRepositoryConfig .REPO = mock (CsrfTokenRepository .class );
310
315
DefaultCsrfToken csrfToken = new DefaultCsrfToken ("X-CSRF-TOKEN" , "_csrf" , "token" );
316
+ given (CsrfTokenRepositoryConfig .REPO .loadToken (any ())).willReturn (csrfToken );
311
317
given (CsrfTokenRepositoryConfig .REPO .loadDeferredToken (any (HttpServletRequest .class ),
312
318
any (HttpServletResponse .class ))).willReturn (new TestDeferredCsrfToken (csrfToken ));
313
319
this .spring .register (CsrfTokenRepositoryConfig .class , BasicController .class ).autowire ();
@@ -318,6 +324,7 @@ public void loginWhenCustomCsrfTokenRepositoryThenCsrfTokenIsCleared() throws Ex
318
324
.param ("password" , "password" );
319
325
// @formatter:on
320
326
this .mvc .perform (loginRequest ).andExpect (redirectedUrl ("/" ));
327
+ verify (CsrfTokenRepositoryConfig .REPO ).loadToken (any (HttpServletRequest .class ));
321
328
verify (CsrfTokenRepositoryConfig .REPO ).saveToken (isNull (), any (HttpServletRequest .class ),
322
329
any (HttpServletResponse .class ));
323
330
}
@@ -449,6 +456,7 @@ public void getLoginWhenCsrfTokenRequestAttributeHandlerSetThenRespondsWithNorma
449
456
public void loginWhenCsrfTokenRequestAttributeHandlerSetAndNormalCsrfTokenThenSuccess () throws Exception {
450
457
CsrfToken csrfToken = new DefaultCsrfToken ("X-CSRF-TOKEN" , "_csrf" , "token" );
451
458
CsrfTokenRepository csrfTokenRepository = mock (CsrfTokenRepository .class );
459
+ given (csrfTokenRepository .loadToken (any (HttpServletRequest .class ))).willReturn (csrfToken );
452
460
given (csrfTokenRepository .loadDeferredToken (any (HttpServletRequest .class ), any (HttpServletResponse .class )))
453
461
.willReturn (new TestDeferredCsrfToken (csrfToken ));
454
462
CsrfTokenRequestHandlerConfig .REPO = csrfTokenRepository ;
@@ -462,6 +470,7 @@ public void loginWhenCsrfTokenRequestAttributeHandlerSetAndNormalCsrfTokenThenSu
462
470
.param ("password" , "password" );
463
471
// @formatter:on
464
472
this .mvc .perform (loginRequest ).andExpect (redirectedUrl ("/" ));
473
+ verify (csrfTokenRepository ).loadToken (any (HttpServletRequest .class ));
465
474
verify (csrfTokenRepository ).saveToken (isNull (), any (HttpServletRequest .class ), any (HttpServletResponse .class ));
466
475
verify (csrfTokenRepository , times (2 )).loadDeferredToken (any (HttpServletRequest .class ),
467
476
any (HttpServletResponse .class ));
@@ -487,6 +496,7 @@ public void getLoginWhenXorCsrfTokenRequestAttributeHandlerSetThenRespondsWithMa
487
496
public void loginWhenXorCsrfTokenRequestAttributeHandlerSetAndMaskedCsrfTokenThenSuccess () throws Exception {
488
497
CsrfToken csrfToken = new DefaultCsrfToken ("X-CSRF-TOKEN" , "_csrf" , "token" );
489
498
CsrfTokenRepository csrfTokenRepository = mock (CsrfTokenRepository .class );
499
+ given (csrfTokenRepository .loadToken (any (HttpServletRequest .class ))).willReturn (csrfToken );
490
500
given (csrfTokenRepository .loadDeferredToken (any (HttpServletRequest .class ), any (HttpServletResponse .class )))
491
501
.willReturn (new TestDeferredCsrfToken (csrfToken ));
492
502
CsrfTokenRequestHandlerConfig .REPO = csrfTokenRepository ;
@@ -503,12 +513,93 @@ public void loginWhenXorCsrfTokenRequestAttributeHandlerSetAndMaskedCsrfTokenThe
503
513
.param ("password" , "password" );
504
514
// @formatter:on
505
515
this .mvc .perform (loginRequest ).andExpect (redirectedUrl ("/" ));
516
+ verify (csrfTokenRepository ).loadToken (any (HttpServletRequest .class ));
506
517
verify (csrfTokenRepository ).saveToken (isNull (), any (HttpServletRequest .class ), any (HttpServletResponse .class ));
507
518
verify (csrfTokenRepository , times (3 )).loadDeferredToken (any (HttpServletRequest .class ),
508
519
any (HttpServletResponse .class ));
509
520
verifyNoMoreInteractions (csrfTokenRepository );
510
521
}
511
522
523
+ @ Test
524
+ public void loginWhenFormLoginAndCookieCsrfTokenRepositorySetAndExistingTokenThenRemovesAndGeneratesNewToken ()
525
+ throws Exception {
526
+ CsrfToken csrfToken = new DefaultCsrfToken ("X-XSRF-TOKEN" , "_csrf" , "token" );
527
+ Cookie existingCookie = new Cookie ("XSRF-TOKEN" , csrfToken .getToken ());
528
+ CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository .withHttpOnlyFalse ();
529
+ csrfTokenRepository .setCookieName (existingCookie .getName ());
530
+ CsrfTokenRequestHandlerConfig .REPO = csrfTokenRepository ;
531
+ CsrfTokenRequestHandlerConfig .HANDLER = new CsrfTokenRequestAttributeHandler ();
532
+ this .spring .register (CsrfTokenRequestHandlerConfig .class , BasicController .class ).autowire ();
533
+
534
+ // @formatter:off
535
+ MockHttpServletRequestBuilder loginRequest = post ("/login" )
536
+ .cookie (existingCookie )
537
+ .header (csrfToken .getHeaderName (), csrfToken .getToken ())
538
+ .param ("username" , "user" )
539
+ .param ("password" , "password" );
540
+ // @formatter:on
541
+ MvcResult mvcResult = this .mvc .perform (loginRequest ).andExpect (redirectedUrl ("/" )).andReturn ();
542
+ List <Cookie > cookies = Arrays .asList (mvcResult .getResponse ().getCookies ());
543
+ cookies .removeIf ((cookie ) -> !cookie .getName ().equalsIgnoreCase (existingCookie .getName ()));
544
+ assertThat (cookies ).hasSize (2 );
545
+ assertThat (cookies .get (0 ).getValue ()).isEmpty ();
546
+ assertThat (cookies .get (1 ).getValue ()).isNotEmpty ();
547
+ }
548
+
549
+ @ Test
550
+ public void postWhenHttpBasicAndCookieCsrfTokenRepositorySetAndExistingTokenThenRemovesAndGeneratesNewToken ()
551
+ throws Exception {
552
+ CsrfToken csrfToken = new DefaultCsrfToken ("X-XSRF-TOKEN" , "_csrf" , "token" );
553
+ Cookie existingCookie = new Cookie ("XSRF-TOKEN" , csrfToken .getToken ());
554
+ CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository .withHttpOnlyFalse ();
555
+ csrfTokenRepository .setCookieName (existingCookie .getName ());
556
+ HttpBasicCsrfTokenRequestHandlerConfig .REPO = csrfTokenRepository ;
557
+ HttpBasicCsrfTokenRequestHandlerConfig .HANDLER = new CsrfTokenRequestAttributeHandler ();
558
+ this .spring .register (HttpBasicCsrfTokenRequestHandlerConfig .class , BasicController .class ).autowire ();
559
+
560
+ HttpHeaders headers = new HttpHeaders ();
561
+ headers .set (csrfToken .getHeaderName (), csrfToken .getToken ());
562
+ headers .setBasicAuth ("user" , "password" );
563
+ // @formatter:off
564
+ MvcResult mvcResult = this .mvc .perform (post ("/" )
565
+ .cookie (existingCookie )
566
+ .headers (headers ))
567
+ .andExpect (status ().isOk ())
568
+ .andReturn ();
569
+ // @formatter:on
570
+ List <Cookie > cookies = Arrays .asList (mvcResult .getResponse ().getCookies ());
571
+ cookies .removeIf ((cookie ) -> !cookie .getName ().equalsIgnoreCase (existingCookie .getName ()));
572
+ assertThat (cookies ).hasSize (2 );
573
+ assertThat (cookies .get (0 ).getValue ()).isEmpty ();
574
+ assertThat (cookies .get (1 ).getValue ()).isNotEmpty ();
575
+ }
576
+
577
+ @ Test
578
+ public void getWhenHttpBasicAndCookieCsrfTokenRepositorySetAndNoExistingCookieThenGeneratesNewToken ()
579
+ throws Exception {
580
+ CsrfToken csrfToken = new DefaultCsrfToken ("X-XSRF-TOKEN" , "_csrf" , "token" );
581
+ Cookie expectedCookie = new Cookie ("XSRF-TOKEN" , csrfToken .getToken ());
582
+ CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository .withHttpOnlyFalse ();
583
+ csrfTokenRepository .setCookieName (expectedCookie .getName ());
584
+ HttpBasicCsrfTokenRequestHandlerConfig .REPO = csrfTokenRepository ;
585
+ HttpBasicCsrfTokenRequestHandlerConfig .HANDLER = new CsrfTokenRequestAttributeHandler ();
586
+ this .spring .register (HttpBasicCsrfTokenRequestHandlerConfig .class , BasicController .class ).autowire ();
587
+
588
+ HttpHeaders headers = new HttpHeaders ();
589
+ headers .set (csrfToken .getHeaderName (), csrfToken .getToken ());
590
+ headers .setBasicAuth ("user" , "password" );
591
+ // @formatter:off
592
+ MvcResult mvcResult = this .mvc .perform (get ("/" )
593
+ .headers (headers ))
594
+ .andExpect (status ().isOk ())
595
+ .andReturn ();
596
+ // @formatter:on
597
+ List <Cookie > cookies = Arrays .asList (mvcResult .getResponse ().getCookies ());
598
+ cookies .removeIf ((cookie ) -> !cookie .getName ().equalsIgnoreCase (expectedCookie .getName ()));
599
+ assertThat (cookies ).hasSize (1 );
600
+ assertThat (cookies .get (0 ).getValue ()).isNotEmpty ();
601
+ }
602
+
512
603
@ Configuration
513
604
static class AllowHttpMethodsFirewallConfig {
514
605
@@ -902,6 +993,42 @@ void configure(AuthenticationManagerBuilder auth) throws Exception {
902
993
903
994
}
904
995
996
+ @ Configuration
997
+ @ EnableWebSecurity
998
+ static class HttpBasicCsrfTokenRequestHandlerConfig {
999
+
1000
+ static CsrfTokenRepository REPO ;
1001
+
1002
+ static CsrfTokenRequestHandler HANDLER ;
1003
+
1004
+ @ Bean
1005
+ SecurityFilterChain securityFilterChain (HttpSecurity http ) throws Exception {
1006
+ // @formatter:off
1007
+ http
1008
+ .authorizeHttpRequests ((authorize ) -> authorize
1009
+ .anyRequest ().authenticated ()
1010
+ )
1011
+ .httpBasic (Customizer .withDefaults ())
1012
+ .csrf ((csrf ) -> csrf
1013
+ .csrfTokenRepository (REPO )
1014
+ .csrfTokenRequestHandler (HANDLER )
1015
+ );
1016
+ // @formatter:on
1017
+
1018
+ return http .build ();
1019
+ }
1020
+
1021
+ @ Autowired
1022
+ void configure (AuthenticationManagerBuilder auth ) throws Exception {
1023
+ // @formatter:off
1024
+ auth
1025
+ .inMemoryAuthentication ()
1026
+ .withUser (PasswordEncodedUser .user ());
1027
+ // @formatter:on
1028
+ }
1029
+
1030
+ }
1031
+
905
1032
@ RestController
906
1033
static class BasicController {
907
1034
0 commit comments