Skip to content

Commit 2407d07

Browse files
author
Steve Riesenberg
committed
Default to Xor CSRF tokens in CsrfWebFilter
Closes gh-11960
1 parent 2a2051c commit 2407d07

File tree

3 files changed

+33
-18
lines changed

3 files changed

+33
-18
lines changed

config/src/test/kotlin/org/springframework/security/config/web/server/ServerCsrfDslTests.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ import org.springframework.context.annotation.Bean
2727
import org.springframework.context.annotation.Configuration
2828
import org.springframework.http.HttpStatus
2929
import org.springframework.http.MediaType
30+
import org.springframework.mock.http.server.reactive.MockServerHttpRequest
31+
import org.springframework.mock.web.server.MockServerWebExchange
3032
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
3133
import org.springframework.security.config.test.SpringTestContext
3234
import org.springframework.security.config.test.SpringTestContextExtension
@@ -39,6 +41,7 @@ import org.springframework.security.web.server.csrf.ServerCsrfTokenRepository
3941
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler
4042
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestHandler
4143
import org.springframework.security.web.server.csrf.WebSessionServerCsrfTokenRepository
44+
import org.springframework.security.web.server.csrf.XorServerCsrfTokenRequestAttributeHandler
4245
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher
4346
import org.springframework.test.web.reactive.server.WebTestClient
4447
import org.springframework.web.bind.annotation.PostMapping
@@ -278,14 +281,23 @@ class ServerCsrfDslTests {
278281
MultipartFormDataEnabledConfig.TOKEN_REPOSITORY.generateToken(any())
279282
} returns Mono.just(this.token)
280283

284+
val csrfToken = createXorCsrfToken()
281285
this.client.post()
282286
.uri("/")
283287
.contentType(MediaType.MULTIPART_FORM_DATA)
284-
.body(fromMultipartData(this.token.parameterName, this.token.token))
288+
.body(fromMultipartData(csrfToken.parameterName, csrfToken.token))
285289
.exchange()
286290
.expectStatus().isOk
287291
}
288292

293+
private fun createXorCsrfToken(): CsrfToken {
294+
val handler = XorServerCsrfTokenRequestAttributeHandler()
295+
val exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"))
296+
handler.handle(exchange, Mono.just(this.token))
297+
val deferredCsrfToken: Mono<CsrfToken>? = exchange.getAttribute(CsrfToken::class.java.name)
298+
return deferredCsrfToken?.block()!!
299+
}
300+
289301
@Configuration
290302
@EnableWebFluxSecurity
291303
@EnableWebFlux

web/src/main/java/org/springframework/security/web/server/csrf/CsrfWebFilter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ public class CsrfWebFilter implements WebFilter {
8383
private ServerAccessDeniedHandler accessDeniedHandler = new HttpStatusServerAccessDeniedHandler(
8484
HttpStatus.FORBIDDEN);
8585

86-
private ServerCsrfTokenRequestHandler requestHandler = new ServerCsrfTokenRequestAttributeHandler();
86+
private ServerCsrfTokenRequestHandler requestHandler = new XorServerCsrfTokenRequestAttributeHandler();
8787

8888
public void setAccessDeniedHandler(ServerAccessDeniedHandler accessDeniedHandler) {
8989
Assert.notNull(accessDeniedHandler, "accessDeniedHandler");

web/src/test/java/org/springframework/security/web/server/csrf/CsrfWebFilterTests.java

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,10 @@ public void filterWhenPostAndEstablishedCsrfTokenAndRequestParamValidTokenThenCo
125125
this.csrfFilter.setCsrfTokenRepository(this.repository);
126126
given(this.repository.loadToken(any())).willReturn(Mono.just(this.token));
127127
given(this.repository.generateToken(any())).willReturn(Mono.just(this.token));
128+
CsrfToken csrfToken = createXorCsrfToken();
128129
this.post = MockServerWebExchange
129130
.from(MockServerHttpRequest.post("/").contentType(MediaType.APPLICATION_FORM_URLENCODED)
130-
.body(this.token.getParameterName() + "=" + this.token.getToken()));
131+
.body(csrfToken.getParameterName() + "=" + csrfToken.getToken()));
131132
Mono<Void> result = this.csrfFilter.filter(this.post, this.chain);
132133
StepVerifier.create(result).verifyComplete();
133134
chainResult.assertWasSubscribed();
@@ -151,8 +152,9 @@ public void filterWhenPostAndEstablishedCsrfTokenAndHeaderValidTokenThenContinue
151152
this.csrfFilter.setCsrfTokenRepository(this.repository);
152153
given(this.repository.loadToken(any())).willReturn(Mono.just(this.token));
153154
given(this.repository.generateToken(any())).willReturn(Mono.just(this.token));
155+
CsrfToken csrfToken = createXorCsrfToken();
154156
this.post = MockServerWebExchange
155-
.from(MockServerHttpRequest.post("/").header(this.token.getHeaderName(), this.token.getToken()));
157+
.from(MockServerHttpRequest.post("/").header(csrfToken.getHeaderName(), csrfToken.getToken()));
156158
Mono<Void> result = this.csrfFilter.filter(this.post, this.chain);
157159
StepVerifier.create(result).verifyComplete();
158160
chainResult.assertWasSubscribed();
@@ -181,30 +183,22 @@ public void filterWhenRequestHandlerSetThenUsed() {
181183
}
182184

183185
@Test
184-
public void filterWhenXorServerCsrfTokenRequestProcessorAndValidTokenThenSuccess() {
186+
public void filterWhenXorServerCsrfTokenRequestAttributeHandlerAndValidTokenThenSuccess() {
185187
PublisherProbe<Void> chainResult = PublisherProbe.empty();
186188
given(this.chain.filter(any())).willReturn(chainResult.mono());
187189
this.csrfFilter.setCsrfTokenRepository(this.repository);
188190
given(this.repository.generateToken(any())).willReturn(Mono.just(this.token));
189191
given(this.repository.loadToken(any())).willReturn(Mono.just(this.token));
190-
XorServerCsrfTokenRequestAttributeHandler requestHandler = new XorServerCsrfTokenRequestAttributeHandler();
191-
this.csrfFilter.setRequestHandler(requestHandler);
192-
StepVerifier.create(this.csrfFilter.filter(this.get, this.chain)).verifyComplete();
193-
chainResult.assertWasSubscribed();
194-
195-
Mono<CsrfToken> csrfTokenAttribute = this.get.getAttribute(CsrfToken.class.getName());
196-
assertThat(csrfTokenAttribute).isNotNull();
197-
StepVerifier.create(csrfTokenAttribute)
198-
.consumeNextWith((csrfToken) -> this.post = MockServerWebExchange
199-
.from(MockServerHttpRequest.post("/").header(csrfToken.getHeaderName(), csrfToken.getToken())))
200-
.verifyComplete();
201192

193+
CsrfToken csrfToken = createXorCsrfToken();
194+
this.post = MockServerWebExchange
195+
.from(MockServerHttpRequest.post("/").header(csrfToken.getHeaderName(), csrfToken.getToken()));
202196
StepVerifier.create(this.csrfFilter.filter(this.post, this.chain)).verifyComplete();
203197
chainResult.assertWasSubscribed();
204198
}
205199

206200
@Test
207-
public void filterWhenXorServerCsrfTokenRequestProcessorAndRawTokenThenAccessDeniedException() {
201+
public void filterWhenXorServerCsrfTokenRequestAttributeHandlerAndRawTokenThenAccessDeniedException() {
208202
PublisherProbe<Void> chainResult = PublisherProbe.empty();
209203
this.csrfFilter.setCsrfTokenRepository(this.repository);
210204
given(this.repository.loadToken(any())).willReturn(Mono.just(this.token));
@@ -305,6 +299,7 @@ public void filterWhenMultipartMixedAndEnabledThenNotRead() {
305299
}
306300

307301
// gh-9561
302+
308303
@Test
309304
public void doFilterWhenTokenIsNullThenNoNullPointer() {
310305
this.csrfFilter.setCsrfTokenRepository(this.repository);
@@ -318,8 +313,8 @@ public void doFilterWhenTokenIsNullThenNoNullPointer() {
318313
.bodyValue(this.token.getParameterName() + "=" + this.token.getToken()).exchange().expectStatus()
319314
.isForbidden();
320315
}
321-
322316
// gh-9113
317+
323318
@Test
324319
public void filterWhenSubscribingCsrfTokenMultipleTimesThenGenerateOnlyOnce() {
325320
PublisherProbe<CsrfToken> chainResult = PublisherProbe.empty();
@@ -334,6 +329,14 @@ public void filterWhenSubscribingCsrfTokenMultipleTimesThenGenerateOnlyOnce() {
334329
assertThat(chainResult.subscribeCount()).isEqualTo(1);
335330
}
336331

332+
private CsrfToken createXorCsrfToken() {
333+
ServerCsrfTokenRequestHandler handler = new XorServerCsrfTokenRequestAttributeHandler();
334+
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/"));
335+
handler.handle(exchange, Mono.just(this.token));
336+
Mono<CsrfToken> csrfToken = exchange.getAttribute(CsrfToken.class.getName());
337+
return csrfToken.block();
338+
}
339+
337340
@RestController
338341
static class OkController {
339342

0 commit comments

Comments
 (0)