diff --git a/docs/modules/ROOT/pages/reactive/oauth2/login/logout.adoc b/docs/modules/ROOT/pages/reactive/oauth2/login/logout.adoc
index 4cfb7c8222..af11f484d5 100644
--- a/docs/modules/ROOT/pages/reactive/oauth2/login/logout.adoc
+++ b/docs/modules/ROOT/pages/reactive/oauth2/login/logout.adoc
@@ -126,7 +126,7 @@ If used, the application's base URL, such as `https://app.example.org`, replaces
[NOTE]
====
By default, `OidcClientInitiatedServerLogoutSuccessHandler` redirects to the logout URL using a standard HTTP redirect with the `GET` method.
-To perform the logout using a `POST` request, set the redirect strategy to `ServerFormPostRedirectStrategy`, for example with `OidcClientInitiatedServerLogoutSuccessHandler.setRedirectStrategy(new ServerFormPostRedirectStrategy())`.
+To perform the logout using a `POST` request, set the redirect strategy to `FormPostServerRedirectStrategy`, for example with `OidcClientInitiatedServerLogoutSuccessHandler.setRedirectStrategy(new ServerFormPostRedirectStrategy())`.
====
[[configure-provider-initiated-oidc-logout]]
diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java
index 65c9bcdd51..682ee3819a 100644
--- a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java
+++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/web/server/logout/OidcClientInitiatedServerLogoutSuccessHandlerTests.java
@@ -229,7 +229,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandlerTests {
}
@Test
- public void logoutWhenCustomRedirectStrategySetThenCustomRedirectStrategyUse() {
+ public void logoutWhenCustomRedirectStrategySetThenCustomRedirectStrategyUsed() {
ServerRedirectStrategy redirectStrategy = mock(ServerRedirectStrategy.class);
given(redirectStrategy.sendRedirect(any(), any())).willReturn(Mono.empty());
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(),
diff --git a/web/src/main/java/org/springframework/security/web/server/ServerFormPostRedirectStrategy.java b/web/src/main/java/org/springframework/security/web/server/FormPostServerRedirectStrategy.java
similarity index 71%
rename from web/src/main/java/org/springframework/security/web/server/ServerFormPostRedirectStrategy.java
rename to web/src/main/java/org/springframework/security/web/server/FormPostServerRedirectStrategy.java
index 2836f9ca85..b6f2711ea0 100644
--- a/web/src/main/java/org/springframework/security/web/server/ServerFormPostRedirectStrategy.java
+++ b/web/src/main/java/org/springframework/security/web/server/FormPostServerRedirectStrategy.java
@@ -26,6 +26,7 @@ import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
@@ -41,15 +42,13 @@ import org.springframework.web.util.UriComponentsBuilder;
* data instead of query string data.
*
* @author Max Batischev
+ * @author Steve Riesenberg
* @since 6.5
*/
-public final class ServerFormPostRedirectStrategy implements ServerRedirectStrategy {
+public final class FormPostServerRedirectStrategy implements ServerRedirectStrategy {
private static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
- private static final StringKeyGenerator DEFAULT_NONCE_GENERATOR = new Base64StringKeyGenerator(
- Base64.getUrlEncoder().withoutPadding(), 96);
-
private static final String REDIRECT_PAGE_TEMPLATE = """
@@ -79,46 +78,46 @@ public final class ServerFormPostRedirectStrategy implements ServerRedirectStrat
""";
+ private static final StringKeyGenerator DEFAULT_NONCE_GENERATOR = new Base64StringKeyGenerator(
+ Base64.getUrlEncoder().withoutPadding(), 96);
+
@Override
public Mono sendRedirect(ServerWebExchange exchange, URI location) {
- String nonce = DEFAULT_NONCE_GENERATOR.generateKey();
- String policyDirective = "script-src 'nonce-%s'".formatted(nonce);
+ final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(location);
- ServerHttpResponse response = exchange.getResponse();
- response.setStatusCode(HttpStatus.OK);
- response.getHeaders().setContentType(MediaType.TEXT_HTML);
- response.getHeaders().add(CONTENT_SECURITY_POLICY_HEADER, policyDirective);
- return response.writeWith(createBuffer(exchange, location, nonce));
- }
-
- private Mono createBuffer(ServerWebExchange exchange, URI location, String nonce) {
- byte[] bytes = createPage(location, nonce);
- DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
- return Mono.just(bufferFactory.wrap(bytes));
- }
-
- private byte[] createPage(URI location, String nonce) {
- UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(location);
-
- StringBuilder hiddenInputsHtmlBuilder = new StringBuilder();
+ final StringBuilder hiddenInputsHtmlBuilder = new StringBuilder();
for (final Map.Entry> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
final String name = entry.getKey();
for (final String value : entry.getValue()) {
// @formatter:off
final String hiddenInput = HIDDEN_INPUT_TEMPLATE
- .replace("{{name}}", HtmlUtils.htmlEscape(name))
- .replace("{{value}}", HtmlUtils.htmlEscape(value));
+ .replace("{{name}}", HtmlUtils.htmlEscape(name))
+ .replace("{{value}}", HtmlUtils.htmlEscape(value));
// @formatter:on
hiddenInputsHtmlBuilder.append(hiddenInput.trim());
}
}
+
+ // Create the script-src policy directive for the Content-Security-Policy header
+ final String nonce = DEFAULT_NONCE_GENERATOR.generateKey();
+ final String policyDirective = "script-src 'nonce-%s'".formatted(nonce);
+
// @formatter:off
- return REDIRECT_PAGE_TEMPLATE
- .replace("{{action}}", HtmlUtils.htmlEscape(uriComponentsBuilder.query(null).build().toUriString()))
- .replace("{{params}}", hiddenInputsHtmlBuilder.toString())
- .replace("{{nonce}}", HtmlUtils.htmlEscape(nonce))
- .getBytes(StandardCharsets.UTF_8);
+ final String html = REDIRECT_PAGE_TEMPLATE
+ // Clear the query string as we don't want that to be part of the form action URL
+ .replace("{{action}}", HtmlUtils.htmlEscape(uriComponentsBuilder.query(null).build().toUriString()))
+ .replace("{{params}}", hiddenInputsHtmlBuilder.toString())
+ .replace("{{nonce}}", HtmlUtils.htmlEscape(nonce));
// @formatter:on
+
+ final ServerHttpResponse response = exchange.getResponse();
+ response.setStatusCode(HttpStatus.OK);
+ response.getHeaders().setContentType(MediaType.TEXT_HTML);
+ response.getHeaders().set(CONTENT_SECURITY_POLICY_HEADER, policyDirective);
+
+ final DataBufferFactory bufferFactory = response.bufferFactory();
+ final DataBuffer buffer = bufferFactory.wrap(html.getBytes(StandardCharsets.UTF_8));
+ return response.writeWith(Mono.just(buffer)).doOnError((error) -> DataBufferUtils.release(buffer));
}
}
diff --git a/web/src/test/java/org/springframework/security/web/server/ServerFormPostRedirectStrategyTests.java b/web/src/test/java/org/springframework/security/web/server/FormPostServerRedirectStrategyTests.java
similarity index 93%
rename from web/src/test/java/org/springframework/security/web/server/ServerFormPostRedirectStrategyTests.java
rename to web/src/test/java/org/springframework/security/web/server/FormPostServerRedirectStrategyTests.java
index 67d65d2ab0..1a2124bb98 100644
--- a/web/src/test/java/org/springframework/security/web/server/ServerFormPostRedirectStrategyTests.java
+++ b/web/src/test/java/org/springframework/security/web/server/FormPostServerRedirectStrategyTests.java
@@ -30,15 +30,15 @@ import org.springframework.mock.web.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
/**
- * Tests for {@link ServerFormPostRedirectStrategy}.
+ * Tests for {@link FormPostServerRedirectStrategy}.
*
* @author Max Batischev
*/
-public class ServerFormPostRedirectStrategyTests {
+public class FormPostServerRedirectStrategyTests {
private static final String POLICY_DIRECTIVE_PATTERN = "script-src 'nonce-(.+)'";
- private final ServerRedirectStrategy redirectStrategy = new ServerFormPostRedirectStrategy();
+ private final ServerRedirectStrategy redirectStrategy = new FormPostServerRedirectStrategy();
private final MockServerHttpRequest request = MockServerHttpRequest.get("https://localhost").build();
@@ -89,7 +89,7 @@ public class ServerFormPostRedirectStrategyTests {
}
@Test
- public void redirectWhenLocationAbsoluteUilWithQueryParamsIsPresentThenRedirect() {
+ public void redirectWhenLocationAbsoluteUriWithQueryParamsIsPresentThenRedirect() {
this.redirectStrategy
.sendRedirect(this.webExchange, URI.create("https://example.com/path?param1=one¶m2=two#fragment"))
.block();
@@ -105,7 +105,7 @@ public class ServerFormPostRedirectStrategyTests {
private ThrowingConsumer hasScriptSrcNonce() {
return (response) -> {
- final String policyDirective = response.getHeaders().get("Content-Security-Policy").get(0);
+ final String policyDirective = response.getHeaders().getFirst("Content-Security-Policy");
assertThat(policyDirective).isNotEmpty();
assertThat(policyDirective).matches(POLICY_DIRECTIVE_PATTERN);