Skip to content

Commit 0c8ff77

Browse files
author
Steve Riesenberg
committed
Add tests
1 parent ec8c3ba commit 0c8ff77

File tree

10 files changed

+603
-7
lines changed

10 files changed

+603
-7
lines changed

Diff for: config/src/test/java/org/springframework/security/config/annotation/web/configurers/CsrfConfigurerTests.java

+152-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import org.springframework.context.annotation.Configuration;
3131
import org.springframework.http.HttpMethod;
3232
import org.springframework.mock.web.MockHttpSession;
33+
import org.springframework.security.config.Customizer;
3334
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
3435
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
3536
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -38,10 +39,14 @@
3839
import org.springframework.security.config.test.SpringTestContextExtension;
3940
import org.springframework.security.core.Authentication;
4041
import org.springframework.security.core.userdetails.PasswordEncodedUser;
42+
import org.springframework.security.web.SecurityFilterChain;
4143
import org.springframework.security.web.access.AccessDeniedHandler;
4244
import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
45+
import org.springframework.security.web.csrf.CsrfToken;
4346
import org.springframework.security.web.csrf.CsrfTokenRepository;
47+
import org.springframework.security.web.csrf.CsrfTokenRequestProcessor;
4448
import org.springframework.security.web.csrf.DefaultCsrfToken;
49+
import org.springframework.security.web.csrf.XorCsrfTokenRequestProcessor;
4550
import org.springframework.security.web.firewall.StrictHttpFirewall;
4651
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
4752
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -55,12 +60,17 @@
5560

5661
import static org.assertj.core.api.Assertions.assertThat;
5762
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
63+
import static org.hamcrest.Matchers.containsString;
64+
import static org.hamcrest.Matchers.not;
5865
import static org.mockito.ArgumentMatchers.any;
66+
import static org.mockito.ArgumentMatchers.eq;
5967
import static org.mockito.ArgumentMatchers.isNull;
6068
import static org.mockito.BDDMockito.given;
6169
import static org.mockito.Mockito.atLeastOnce;
6270
import static org.mockito.Mockito.mock;
71+
import static org.mockito.Mockito.times;
6372
import static org.mockito.Mockito.verify;
73+
import static org.mockito.Mockito.verifyNoMoreInteractions;
6474
import static org.springframework.security.config.Customizer.withDefaults;
6575
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
6676
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -74,6 +84,7 @@
7484
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
7585
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
7686
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
87+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
7788
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
7889
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
7990

@@ -84,6 +95,7 @@
8495
* @author Eleftheria Stein
8596
* @author Michael Vitz
8697
* @author Sam Simmons
98+
* @author Steve Riesenberg
8799
*/
88100
@ExtendWith(SpringTestContextExtension.class)
89101
public class CsrfConfigurerTests {
@@ -407,6 +419,108 @@ public void csrfAuthenticationStrategyConfiguredThenStrategyUsed() throws Except
407419
any(HttpServletRequest.class), any(HttpServletResponse.class));
408420
}
409421

422+
@Test
423+
public void getLoginWhenCsrfTokenRequestProcessorSetThenRespondsWithNormalCsrfToken() throws Exception {
424+
CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
425+
CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
426+
given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
427+
CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
428+
CsrfTokenRequestProcessorConfig.PROCESSOR = new CsrfTokenRequestProcessor();
429+
this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
430+
this.mvc.perform(get("/login")).andExpect(status().isOk())
431+
.andExpect(content().string(containsString(csrfToken.getToken())));
432+
verify(csrfTokenRepository).loadToken(any(HttpServletRequest.class));
433+
verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
434+
verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
435+
any(HttpServletResponse.class));
436+
verifyNoMoreInteractions(csrfTokenRepository);
437+
}
438+
439+
@Test
440+
public void loginWhenCsrfTokenRequestProcessorSetAndNormalCsrfTokenThenSuccess() throws Exception {
441+
CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
442+
CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
443+
given(csrfTokenRepository.loadToken(any(HttpServletRequest.class))).willReturn(csrfToken);
444+
given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
445+
CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
446+
CsrfTokenRequestProcessorConfig.PROCESSOR = new CsrfTokenRequestProcessor();
447+
this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
448+
// @formatter:off
449+
MockHttpServletRequestBuilder loginRequest = post("/login")
450+
.header(csrfToken.getHeaderName(), csrfToken.getToken())
451+
.param("username", "user")
452+
.param("password", "password");
453+
// @formatter:on
454+
this.mvc.perform(loginRequest).andExpect(redirectedUrl("/"));
455+
verify(csrfTokenRepository, times(2)).loadToken(any(HttpServletRequest.class));
456+
verify(csrfTokenRepository).saveToken(isNull(), any(HttpServletRequest.class), any(HttpServletResponse.class));
457+
verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
458+
verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
459+
any(HttpServletResponse.class));
460+
verifyNoMoreInteractions(csrfTokenRepository);
461+
}
462+
463+
@Test
464+
public void getLoginWhenXorCsrfTokenRequestProcessorSetThenRespondsWithXorCsrfToken() throws Exception {
465+
CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
466+
CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
467+
given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
468+
CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
469+
CsrfTokenRequestProcessorConfig.PROCESSOR = new XorCsrfTokenRequestProcessor();
470+
this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
471+
this.mvc.perform(get("/login")).andExpect(status().isOk())
472+
.andExpect(content().string(containsString(csrfToken.getParameterName())))
473+
.andExpect(content().string(not(containsString(csrfToken.getToken()))));
474+
verify(csrfTokenRepository).loadToken(any(HttpServletRequest.class));
475+
verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
476+
verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
477+
any(HttpServletResponse.class));
478+
verifyNoMoreInteractions(csrfTokenRepository);
479+
}
480+
481+
@Test
482+
public void loginWhenXorCsrfTokenRequestProcessorSetAndXorCsrfTokenThenSuccess() throws Exception {
483+
CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
484+
CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
485+
given(csrfTokenRepository.loadToken(any(HttpServletRequest.class))).willReturn(csrfToken);
486+
given(csrfTokenRepository.generateToken(any(HttpServletRequest.class))).willReturn(csrfToken);
487+
CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
488+
CsrfTokenRequestProcessorConfig.PROCESSOR = new XorCsrfTokenRequestProcessor();
489+
this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
490+
// @formatter:off
491+
MockHttpServletRequestBuilder loginRequest = post("/login")
492+
.header(csrfToken.getHeaderName(), "sKCSGVHEz_l8Pw==")
493+
.param("username", "user")
494+
.param("password", "password");
495+
// @formatter:on
496+
this.mvc.perform(loginRequest).andExpect(redirectedUrl("/"));
497+
verify(csrfTokenRepository, times(2)).loadToken(any(HttpServletRequest.class));
498+
verify(csrfTokenRepository).saveToken(isNull(), any(HttpServletRequest.class), any(HttpServletResponse.class));
499+
verify(csrfTokenRepository).generateToken(any(HttpServletRequest.class));
500+
verify(csrfTokenRepository).saveToken(eq(csrfToken), any(HttpServletRequest.class),
501+
any(HttpServletResponse.class));
502+
verifyNoMoreInteractions(csrfTokenRepository);
503+
}
504+
505+
@Test
506+
public void loginWhenXorCsrfTokenRequestProcessorSetAndNormalCsrfTokenThenRespondsWithForbidden() throws Exception {
507+
CsrfToken csrfToken = new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", "token");
508+
CsrfTokenRepository csrfTokenRepository = mock(CsrfTokenRepository.class);
509+
given(csrfTokenRepository.loadToken(any(HttpServletRequest.class))).willReturn(csrfToken);
510+
CsrfTokenRequestProcessorConfig.REPO = csrfTokenRepository;
511+
CsrfTokenRequestProcessorConfig.PROCESSOR = new XorCsrfTokenRequestProcessor();
512+
this.spring.register(CsrfTokenRequestProcessorConfig.class, BasicController.class).autowire();
513+
// @formatter:off
514+
MockHttpServletRequestBuilder loginRequest = post("/login")
515+
.header(csrfToken.getHeaderName(), csrfToken.getToken())
516+
.param("username", "user")
517+
.param("password", "password");
518+
// @formatter:on
519+
this.mvc.perform(loginRequest).andExpect(status().isForbidden());
520+
verify(csrfTokenRepository).loadToken(any(HttpServletRequest.class));
521+
verifyNoMoreInteractions(csrfTokenRepository);
522+
}
523+
410524
@Configuration
411525
static class AllowHttpMethodsFirewallConfig {
412526

@@ -748,6 +862,43 @@ protected void configure(AuthenticationManagerBuilder auth) throws Exception {
748862

749863
}
750864

865+
@Configuration
866+
@EnableWebSecurity
867+
static class CsrfTokenRequestProcessorConfig {
868+
869+
static CsrfTokenRepository REPO;
870+
871+
static CsrfTokenRequestProcessor PROCESSOR;
872+
873+
@Bean
874+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
875+
// @formatter:off
876+
http
877+
.authorizeHttpRequests((authorize) -> authorize
878+
.anyRequest().authenticated()
879+
)
880+
.formLogin(Customizer.withDefaults())
881+
.csrf((csrf) -> csrf
882+
.csrfTokenRepository(REPO)
883+
.csrfTokenRequestAttributeHandler(PROCESSOR)
884+
.csrfTokenRequestResolver(PROCESSOR)
885+
);
886+
// @formatter:on
887+
888+
return http.build();
889+
}
890+
891+
@Autowired
892+
void configure(AuthenticationManagerBuilder auth) throws Exception {
893+
// @formatter:off
894+
auth
895+
.inMemoryAuthentication()
896+
.withUser(PasswordEncodedUser.user());
897+
// @formatter:on
898+
}
899+
900+
}
901+
751902
@RestController
752903
static class BasicController {
753904

Diff for: config/src/test/java/org/springframework/security/config/http/CsrfConfigTests.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -300,6 +300,39 @@ public void getWhenUsingCsrfAndCustomRequestAttributeThenSetUsingCsrfAttrName()
300300
// @formatter:on
301301
}
302302

303+
@Test
304+
public void postWhenUsingCsrfAndXorCsrfTokenRequestProcessorThenOk() throws Exception {
305+
this.spring.configLocations(this.xml("WithXorCsrfTokenRequestProcessor"), this.xml("shared-controllers"))
306+
.autowire();
307+
// @formatter:off
308+
MvcResult mvcResult = this.mvc.perform(get("/ok"))
309+
.andExpect(status().isOk())
310+
.andReturn();
311+
MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession();
312+
CsrfToken csrfToken = (CsrfToken) mvcResult.getRequest().getAttribute("_csrf");
313+
MockHttpServletRequestBuilder ok = post("/ok")
314+
.header(csrfToken.getHeaderName(), csrfToken.getToken())
315+
.session(session);
316+
this.mvc.perform(ok).andExpect(status().isOk());
317+
// @formatter:on
318+
}
319+
320+
@Test
321+
public void postWhenUsingCsrfAndXorCsrfTokenRequestProcessorWithRawTokenThenForbidden() throws Exception {
322+
this.spring.configLocations(this.xml("WithXorCsrfTokenRequestProcessor"), this.xml("shared-controllers"))
323+
.autowire();
324+
// @formatter:off
325+
MvcResult mvcResult = this.mvc.perform(get("/ok"))
326+
.andExpect(status().isOk())
327+
.andReturn();
328+
MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession();
329+
MockHttpServletRequestBuilder ok = post("/ok")
330+
.with(csrf())
331+
.session(session);
332+
this.mvc.perform(ok).andExpect(status().isForbidden());
333+
// @formatter:on
334+
}
335+
303336
@Test
304337
public void postWhenHasCsrfTokenButSessionExpiresThenRequestIsCancelledAfterSuccessfulAuthentication()
305338
throws Exception {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2002-2018 the original author or authors.
4+
~
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~
9+
~ https://www.apache.org/licenses/LICENSE-2.0
10+
~
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
-->
17+
18+
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
19+
xmlns:p="http://www.springframework.org/schema/p"
20+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
21+
xmlns="http://www.springframework.org/schema/security"
22+
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
23+
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
24+
25+
<http auto-config="true">
26+
<csrf request-attribute-handler-ref="requestProcessor" request-resolver-ref="requestProcessor"/>
27+
</http>
28+
29+
<b:bean id="requestProcessor" class="org.springframework.security.web.csrf.XorCsrfTokenRequestProcessor"
30+
p:csrfRequestAttributeName="_csrf"/>
31+
<b:import resource="CsrfConfigTests-shared-userservice.xml"/>
32+
</b:beans>

Diff for: web/src/main/java/org/springframework/security/web/csrf/CsrfAuthenticationStrategy.java

+6-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ public final class CsrfAuthenticationStrategy implements SessionAuthenticationSt
3939

4040
private final Log logger = LogFactory.getLog(getClass());
4141

42-
private CsrfTokenRequestAttributeHandler requestAttributeHandler = new CsrfTokenRequestProcessor();
43-
4442
private final CsrfTokenRepository csrfTokenRepository;
4543

44+
private CsrfTokenRequestAttributeHandler requestAttributeHandler = new CsrfTokenRequestProcessor();
45+
4646
/**
4747
* Creates a new instance
4848
* @param csrfTokenRepository the {@link CsrfTokenRepository} to use
@@ -53,10 +53,12 @@ public CsrfAuthenticationStrategy(CsrfTokenRepository csrfTokenRepository) {
5353
}
5454

5555
/**
56-
* TODO
57-
* @param requestAttributeHandler
56+
* Specify a {@link CsrfTokenRequestAttributeHandler} to use for making the
57+
* {@code CsrfToken} available as a request attribute.
58+
* @param requestAttributeHandler the {@link CsrfTokenRequestAttributeHandler} to use
5859
*/
5960
public void setRequestAttributeHandler(CsrfTokenRequestAttributeHandler requestAttributeHandler) {
61+
Assert.notNull(requestAttributeHandler, "requestAttributeHandler cannot be null");
6062
this.requestAttributeHandler = requestAttributeHandler;
6163
}
6264

Diff for: web/src/main/java/org/springframework/security/web/csrf/CsrfTokenRequestProcessor.java

+6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import javax.servlet.http.HttpServletRequest;
2020

21+
import org.springframework.util.Assert;
22+
2123
/**
2224
* An implementation of the {@link CsrfTokenRequestAttributeHandler} and
2325
* {@link CsrfTokenRequestResolver} interfaces that is capable of making the
@@ -45,6 +47,8 @@ public final void setCsrfRequestAttributeName(String csrfRequestAttributeName) {
4547

4648
@Override
4749
public void handle(HttpServletRequest request, CsrfToken csrfToken) {
50+
Assert.notNull(request, "request cannot be null");
51+
Assert.notNull(csrfToken, "csrfToken cannot be null");
4852
request.setAttribute(CsrfToken.class.getName(), csrfToken);
4953
String csrfAttrName = (this.csrfRequestAttributeName != null) ? this.csrfRequestAttributeName
5054
: csrfToken.getParameterName();
@@ -53,6 +57,8 @@ public void handle(HttpServletRequest request, CsrfToken csrfToken) {
5357

5458
@Override
5559
public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
60+
Assert.notNull(request, "request cannot be null");
61+
Assert.notNull(csrfToken, "csrfToken cannot be null");
5662
String actualToken = request.getHeader(csrfToken.getHeaderName());
5763
if (actualToken == null) {
5864
actualToken = request.getParameter(csrfToken.getParameterName());

Diff for: web/src/main/java/org/springframework/security/web/csrf/XorCsrfTokenRequestProcessor.java

+4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import javax.servlet.http.HttpServletRequest;
2323

2424
import org.springframework.security.crypto.codec.Utf8;
25+
import org.springframework.util.Assert;
2526

2627
/**
2728
* An implementation of the {@link CsrfTokenRequestAttributeHandler} and
@@ -42,11 +43,14 @@ public final class XorCsrfTokenRequestProcessor extends CsrfTokenRequestProcesso
4243
* @param secureRandom the {@code SecureRandom} to use to generate random bytes
4344
*/
4445
public void setSecureRandom(SecureRandom secureRandom) {
46+
Assert.notNull(secureRandom, "secureRandom cannot be null");
4547
this.secureRandom = secureRandom;
4648
}
4749

4850
@Override
4951
public void handle(HttpServletRequest request, CsrfToken csrfToken) {
52+
Assert.notNull(request, "request cannot be null");
53+
Assert.notNull(csrfToken, "csrfToken cannot be null");
5054
String updatedToken = createXoredCsrfToken(this.secureRandom, csrfToken.getToken());
5155
DefaultCsrfToken updatedCsrfToken = new DefaultCsrfToken(csrfToken.getHeaderName(),
5256
csrfToken.getParameterName(), updatedToken);

0 commit comments

Comments
 (0)