mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-02-25 17:06:42 +00:00
I went through everything to get it to fit with Spring's docuemntation standard. Lots of small changes for punctuation, grammar, usage, voice, and so on. Also added some links, mostly to the API Javadoc.
272 lines
12 KiB
Diff
272 lines
12 KiB
Diff
From e2993d93e109c1a3c9020b7ea9efb6e556751ed4 Mon Sep 17 00:00:00 2001
|
|
From: Thomas Vitale <ThomasVitale@users.noreply.github.com>
|
|
Date: Mon, 26 Apr 2021 18:13:20 +0200
|
|
Subject: [PATCH 2/3] Make Csrf cookie secure flag configurable (WebFlux)
|
|
|
|
Make the XSRF-TOKEN cookie secure flag configurable in CookieServerCsrfTokenRepository.
|
|
|
|
Closes gh-9678
|
|
---
|
|
.../csrf/CookieServerCsrfTokenRepository.java | 30 ++++--
|
|
.../CookieServerCsrfTokenRepositoryTests.java | 100 ++++++++++++++++--
|
|
2 files changed, 113 insertions(+), 17 deletions(-)
|
|
|
|
diff --git a/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java b/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java
|
|
index 5910ff3e45..bc3a20e711 100644
|
|
--- a/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java
|
|
+++ b/web/src/main/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepository.java
|
|
@@ -1,5 +1,5 @@
|
|
/*
|
|
- * Copyright 2002-2019 the original author or authors.
|
|
+ * Copyright 2002-2021 the original author or authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
@@ -34,6 +34,7 @@ import org.springframework.web.server.ServerWebExchange;
|
|
* AngularJS. When using with AngularJS be sure to use {@link #withHttpOnlyFalse()} .
|
|
*
|
|
* @author Eric Deandrea
|
|
+ * @author Thomas Vitale
|
|
* @since 5.1
|
|
*/
|
|
public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRepository {
|
|
@@ -54,6 +55,8 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep
|
|
|
|
private boolean cookieHttpOnly = true;
|
|
|
|
+ private Boolean secure;
|
|
+
|
|
/**
|
|
* Factory method to conveniently create an instance that has
|
|
* {@link #setCookieHttpOnly(boolean)} set to false.
|
|
@@ -75,11 +78,16 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep
|
|
public Mono<Void> saveToken(ServerWebExchange exchange, CsrfToken token) {
|
|
return Mono.fromRunnable(() -> {
|
|
String tokenValue = (token != null) ? token.getToken() : "";
|
|
- int maxAge = !tokenValue.isEmpty() ? -1 : 0;
|
|
- String path = (this.cookiePath != null) ? this.cookiePath : getRequestContext(exchange.getRequest());
|
|
- boolean secure = exchange.getRequest().getSslInfo() != null;
|
|
- ResponseCookie cookie = ResponseCookie.from(this.cookieName, tokenValue).domain(this.cookieDomain)
|
|
- .httpOnly(this.cookieHttpOnly).maxAge(maxAge).path(path).secure(secure).build();
|
|
+ // @formatter:off
|
|
+ ResponseCookie cookie = ResponseCookie
|
|
+ .from(this.cookieName, tokenValue)
|
|
+ .domain(this.cookieDomain)
|
|
+ .httpOnly(this.cookieHttpOnly)
|
|
+ .maxAge(!tokenValue.isEmpty() ? -1 : 0)
|
|
+ .path((this.cookiePath != null) ? this.cookiePath : getRequestContext(exchange.getRequest()))
|
|
+ .secure((this.secure != null) ? this.secure : (exchange.getRequest().getSslInfo() != null))
|
|
+ .build();
|
|
+ // @formatter:on
|
|
exchange.getResponse().addCookie(cookie);
|
|
});
|
|
}
|
|
@@ -146,6 +154,16 @@ public final class CookieServerCsrfTokenRepository implements ServerCsrfTokenRep
|
|
this.cookieDomain = cookieDomain;
|
|
}
|
|
|
|
+ /**
|
|
+ * Sets the cookie secure flag. If not set, the value depends on
|
|
+ * {@link ServerHttpRequest#getSslInfo()}.
|
|
+ * @param secure The value for the secure flag
|
|
+ * @since 5.5
|
|
+ */
|
|
+ public void setSecure(boolean secure) {
|
|
+ this.secure = secure;
|
|
+ }
|
|
+
|
|
private CsrfToken createCsrfToken() {
|
|
return createCsrfToken(createNewToken());
|
|
}
|
|
diff --git a/web/src/test/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepositoryTests.java b/web/src/test/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepositoryTests.java
|
|
index d16f131920..7160337053 100644
|
|
--- a/web/src/test/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepositoryTests.java
|
|
+++ b/web/src/test/java/org/springframework/security/web/server/csrf/CookieServerCsrfTokenRepositoryTests.java
|
|
@@ -1,5 +1,5 @@
|
|
/*
|
|
- * Copyright 2002-2018 the original author or authors.
|
|
+ * Copyright 2002-2021 the original author or authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
@@ -16,12 +16,15 @@
|
|
|
|
package org.springframework.security.web.server.csrf;
|
|
|
|
+import java.security.cert.X509Certificate;
|
|
import java.time.Duration;
|
|
|
|
+import org.junit.Before;
|
|
import org.junit.Test;
|
|
|
|
import org.springframework.http.HttpCookie;
|
|
import org.springframework.http.ResponseCookie;
|
|
+import org.springframework.http.server.reactive.SslInfo;
|
|
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
|
import org.springframework.mock.web.server.MockServerWebExchange;
|
|
import org.springframework.util.StringUtils;
|
|
@@ -30,13 +33,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
|
|
|
/**
|
|
* @author Eric Deandrea
|
|
+ * @author Thomas Vitale
|
|
* @since 5.1
|
|
*/
|
|
public class CookieServerCsrfTokenRepositoryTests {
|
|
|
|
- private MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/someUri"));
|
|
+ private CookieServerCsrfTokenRepository csrfTokenRepository;
|
|
|
|
- private CookieServerCsrfTokenRepository csrfTokenRepository = new CookieServerCsrfTokenRepository();
|
|
+ private MockServerHttpRequest.BaseBuilder<?> request;
|
|
|
|
private String expectedHeaderName = CookieServerCsrfTokenRepository.DEFAULT_CSRF_HEADER_NAME;
|
|
|
|
@@ -56,6 +60,12 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
|
|
private String expectedCookieValue = "csrfToken";
|
|
|
|
+ @Before
|
|
+ public void setUp() {
|
|
+ this.csrfTokenRepository = new CookieServerCsrfTokenRepository();
|
|
+ this.request = MockServerHttpRequest.get("/someUri");
|
|
+ }
|
|
+
|
|
@Test
|
|
public void generateTokenWhenDefaultThenDefaults() {
|
|
generateTokenAndAssertExpectedValues();
|
|
@@ -82,8 +92,9 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
|
|
@Test
|
|
public void saveTokenWhenNoSubscriptionThenNotWritten() {
|
|
- this.csrfTokenRepository.saveToken(this.exchange, createToken());
|
|
- assertThat(this.exchange.getResponse().getCookies().getFirst(this.expectedCookieName)).isNull();
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ this.csrfTokenRepository.saveToken(exchange, createToken());
|
|
+ assertThat(exchange.getResponse().getCookies().getFirst(this.expectedCookieName)).isNull();
|
|
}
|
|
|
|
@Test
|
|
@@ -112,6 +123,56 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
saveAndAssertExpectedValues(createToken());
|
|
}
|
|
|
|
+ @Test
|
|
+ public void saveTokenWhenSslInfoPresentThenSecure() {
|
|
+ this.request.sslInfo(new MockSslInfo());
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
|
+ ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
|
+ assertThat(cookie).isNotNull();
|
|
+ assertThat(cookie.isSecure()).isTrue();
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void saveTokenWhenSslInfoNullThenNotSecure() {
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
|
+ ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
|
+ assertThat(cookie).isNotNull();
|
|
+ assertThat(cookie.isSecure()).isFalse();
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void saveTokenWhenSecureFlagTrueThenSecure() {
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ this.csrfTokenRepository.setSecure(true);
|
|
+ this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
|
+ ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
|
+ assertThat(cookie).isNotNull();
|
|
+ assertThat(cookie.isSecure()).isTrue();
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void saveTokenWhenSecureFlagFalseThenNotSecure() {
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ this.csrfTokenRepository.setSecure(false);
|
|
+ this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
|
+ ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
|
+ assertThat(cookie).isNotNull();
|
|
+ assertThat(cookie.isSecure()).isFalse();
|
|
+ }
|
|
+
|
|
+ @Test
|
|
+ public void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecure() {
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ this.request.sslInfo(new MockSslInfo());
|
|
+ this.csrfTokenRepository.setSecure(false);
|
|
+ this.csrfTokenRepository.saveToken(exchange, createToken()).block();
|
|
+ ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
|
+ assertThat(cookie).isNotNull();
|
|
+ assertThat(cookie.isSecure()).isFalse();
|
|
+ }
|
|
+
|
|
@Test
|
|
public void loadTokenWhenCookieExistThenTokenFound() {
|
|
loadAndAssertExpectedValues();
|
|
@@ -127,7 +188,8 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
|
|
@Test
|
|
public void loadTokenWhenNoCookiesThenNullToken() {
|
|
- CsrfToken csrfToken = this.csrfTokenRepository.loadToken(this.exchange).block();
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ CsrfToken csrfToken = this.csrfTokenRepository.loadToken(exchange).block();
|
|
assertThat(csrfToken).isNull();
|
|
}
|
|
|
|
@@ -180,8 +242,8 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
private void loadAndAssertExpectedValues() {
|
|
MockServerHttpRequest.BodyBuilder request = MockServerHttpRequest.post("/someUri")
|
|
.cookie(new HttpCookie(this.expectedCookieName, this.expectedCookieValue));
|
|
- this.exchange = MockServerWebExchange.from(request);
|
|
- CsrfToken csrfToken = this.csrfTokenRepository.loadToken(this.exchange).block();
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(request);
|
|
+ CsrfToken csrfToken = this.csrfTokenRepository.loadToken(exchange).block();
|
|
if (StringUtils.hasText(this.expectedCookieValue)) {
|
|
assertThat(csrfToken).isNotNull();
|
|
assertThat(csrfToken.getHeaderName()).isEqualTo(this.expectedHeaderName);
|
|
@@ -198,8 +260,9 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
this.expectedMaxAge = Duration.ofSeconds(0);
|
|
this.expectedCookieValue = "";
|
|
}
|
|
- this.csrfTokenRepository.saveToken(this.exchange, token).block();
|
|
- ResponseCookie cookie = this.exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ this.csrfTokenRepository.saveToken(exchange, token).block();
|
|
+ ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
|
|
assertThat(cookie).isNotNull();
|
|
assertThat(cookie.getMaxAge()).isEqualTo(this.expectedMaxAge);
|
|
assertThat(cookie.getDomain()).isEqualTo(this.expectedDomain);
|
|
@@ -211,7 +274,8 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
}
|
|
|
|
private void generateTokenAndAssertExpectedValues() {
|
|
- CsrfToken csrfToken = this.csrfTokenRepository.generateToken(this.exchange).block();
|
|
+ MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
|
|
+ CsrfToken csrfToken = this.csrfTokenRepository.generateToken(exchange).block();
|
|
assertThat(csrfToken).isNotNull();
|
|
assertThat(csrfToken.getHeaderName()).isEqualTo(this.expectedHeaderName);
|
|
assertThat(csrfToken.getParameterName()).isEqualTo(this.expectedParameterName);
|
|
@@ -226,4 +290,18 @@ public class CookieServerCsrfTokenRepositoryTests {
|
|
return new DefaultCsrfToken(headerName, parameterName, tokenValue);
|
|
}
|
|
|
|
+ static class MockSslInfo implements SslInfo {
|
|
+
|
|
+ @Override
|
|
+ public String getSessionId() {
|
|
+ return "sessionId";
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public X509Certificate[] getPeerCertificates() {
|
|
+ return new X509Certificate[] {};
|
|
+ }
|
|
+
|
|
+ }
|
|
+
|
|
}
|
|
--
|
|
2.24.1
|
|
|