mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-03-01 02:49:11 +00:00
Polish gh-16551
This commit is contained in:
parent
00cd95be76
commit
7fc5d50adf
@ -126,7 +126,7 @@ If used, the application's base URL, such as `https://app.example.org`, replaces
|
|||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
By default, `OidcClientInitiatedServerLogoutSuccessHandler` redirects to the logout URL using a standard HTTP redirect with the `GET` method.
|
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]]
|
[[configure-provider-initiated-oidc-logout]]
|
||||||
|
@ -229,7 +229,7 @@ public class OidcClientInitiatedServerLogoutSuccessHandlerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void logoutWhenCustomRedirectStrategySetThenCustomRedirectStrategyUse() {
|
public void logoutWhenCustomRedirectStrategySetThenCustomRedirectStrategyUsed() {
|
||||||
ServerRedirectStrategy redirectStrategy = mock(ServerRedirectStrategy.class);
|
ServerRedirectStrategy redirectStrategy = mock(ServerRedirectStrategy.class);
|
||||||
given(redirectStrategy.sendRedirect(any(), any())).willReturn(Mono.empty());
|
given(redirectStrategy.sendRedirect(any(), any())).willReturn(Mono.empty());
|
||||||
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(),
|
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(TestOidcUsers.create(),
|
||||||
|
@ -26,6 +26,7 @@ import reactor.core.publisher.Mono;
|
|||||||
|
|
||||||
import org.springframework.core.io.buffer.DataBuffer;
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
import org.springframework.core.io.buffer.DataBufferFactory;
|
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||||
|
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||||
@ -41,15 +42,13 @@ import org.springframework.web.util.UriComponentsBuilder;
|
|||||||
* data instead of query string data.
|
* data instead of query string data.
|
||||||
*
|
*
|
||||||
* @author Max Batischev
|
* @author Max Batischev
|
||||||
|
* @author Steve Riesenberg
|
||||||
* @since 6.5
|
* @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 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 = """
|
private static final String REDIRECT_PAGE_TEMPLATE = """
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
@ -79,28 +78,14 @@ public final class ServerFormPostRedirectStrategy implements ServerRedirectStrat
|
|||||||
<input name="{{name}}" type="hidden" value="{{value}}" />
|
<input name="{{name}}" type="hidden" value="{{value}}" />
|
||||||
""";
|
""";
|
||||||
|
|
||||||
|
private static final StringKeyGenerator DEFAULT_NONCE_GENERATOR = new Base64StringKeyGenerator(
|
||||||
|
Base64.getUrlEncoder().withoutPadding(), 96);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> sendRedirect(ServerWebExchange exchange, URI location) {
|
public Mono<Void> sendRedirect(ServerWebExchange exchange, URI location) {
|
||||||
String nonce = DEFAULT_NONCE_GENERATOR.generateKey();
|
final UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(location);
|
||||||
String policyDirective = "script-src 'nonce-%s'".formatted(nonce);
|
|
||||||
|
|
||||||
ServerHttpResponse response = exchange.getResponse();
|
final StringBuilder hiddenInputsHtmlBuilder = new StringBuilder();
|
||||||
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<DataBuffer> 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();
|
|
||||||
for (final Map.Entry<String, List<String>> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
|
for (final Map.Entry<String, List<String>> entry : uriComponentsBuilder.build().getQueryParams().entrySet()) {
|
||||||
final String name = entry.getKey();
|
final String name = entry.getKey();
|
||||||
for (final String value : entry.getValue()) {
|
for (final String value : entry.getValue()) {
|
||||||
@ -112,13 +97,27 @@ public final class ServerFormPostRedirectStrategy implements ServerRedirectStrat
|
|||||||
hiddenInputsHtmlBuilder.append(hiddenInput.trim());
|
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
|
// @formatter:off
|
||||||
return REDIRECT_PAGE_TEMPLATE
|
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("{{action}}", HtmlUtils.htmlEscape(uriComponentsBuilder.query(null).build().toUriString()))
|
||||||
.replace("{{params}}", hiddenInputsHtmlBuilder.toString())
|
.replace("{{params}}", hiddenInputsHtmlBuilder.toString())
|
||||||
.replace("{{nonce}}", HtmlUtils.htmlEscape(nonce))
|
.replace("{{nonce}}", HtmlUtils.htmlEscape(nonce));
|
||||||
.getBytes(StandardCharsets.UTF_8);
|
|
||||||
// @formatter:on
|
// @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));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -30,15 +30,15 @@ import org.springframework.mock.web.server.MockServerWebExchange;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ServerFormPostRedirectStrategy}.
|
* Tests for {@link FormPostServerRedirectStrategy}.
|
||||||
*
|
*
|
||||||
* @author Max Batischev
|
* @author Max Batischev
|
||||||
*/
|
*/
|
||||||
public class ServerFormPostRedirectStrategyTests {
|
public class FormPostServerRedirectStrategyTests {
|
||||||
|
|
||||||
private static final String POLICY_DIRECTIVE_PATTERN = "script-src 'nonce-(.+)'";
|
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();
|
private final MockServerHttpRequest request = MockServerHttpRequest.get("https://localhost").build();
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ public class ServerFormPostRedirectStrategyTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void redirectWhenLocationAbsoluteUilWithQueryParamsIsPresentThenRedirect() {
|
public void redirectWhenLocationAbsoluteUriWithQueryParamsIsPresentThenRedirect() {
|
||||||
this.redirectStrategy
|
this.redirectStrategy
|
||||||
.sendRedirect(this.webExchange, URI.create("https://example.com/path?param1=one¶m2=two#fragment"))
|
.sendRedirect(this.webExchange, URI.create("https://example.com/path?param1=one¶m2=two#fragment"))
|
||||||
.block();
|
.block();
|
||||||
@ -105,7 +105,7 @@ public class ServerFormPostRedirectStrategyTests {
|
|||||||
|
|
||||||
private ThrowingConsumer<MockServerHttpResponse> hasScriptSrcNonce() {
|
private ThrowingConsumer<MockServerHttpResponse> hasScriptSrcNonce() {
|
||||||
return (response) -> {
|
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).isNotEmpty();
|
||||||
assertThat(policyDirective).matches(POLICY_DIRECTIVE_PATTERN);
|
assertThat(policyDirective).matches(POLICY_DIRECTIVE_PATTERN);
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user