From e48fdd5ed4e44b01af494df363728e4a426c9cb2 Mon Sep 17 00:00:00 2001 From: Rob Winch <362503+rwinch@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:37:17 -0500 Subject: [PATCH] Use UserWebTestClientConfigurer Closes gh-17496 --- .../config/web/server/ServerX509DslTests.kt | 64 ++----------------- docs/modules/ROOT/nav.adoc | 1 + .../ROOT/pages/reactive/test/web/x509.adoc | 4 ++ .../reactivex509/X509ConfigurationTests.java | 53 +-------------- .../reactivex509/X509ConfigurationTests.kt | 34 +--------- ...erverX509AuthenticationConverterTests.java | 22 +------ .../CookieServerCsrfTokenRepositoryTests.java | 21 +----- 7 files changed, 18 insertions(+), 181 deletions(-) create mode 100644 docs/modules/ROOT/pages/reactive/test/web/x509.adoc diff --git a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerX509DslTests.kt b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerX509DslTests.kt index e82ec296e6..76fe4090ab 100644 --- a/config/src/test/kotlin/org/springframework/security/config/web/server/ServerX509DslTests.kt +++ b/config/src/test/kotlin/org/springframework/security/config/web/server/ServerX509DslTests.kt @@ -16,11 +16,6 @@ package org.springframework.security.config.web.server -import io.mockk.every -import io.mockk.mockk -import java.security.cert.Certificate -import java.security.cert.CertificateFactory -import java.security.cert.X509Certificate import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith import org.springframework.beans.factory.annotation.Autowired @@ -28,10 +23,6 @@ import org.springframework.context.ApplicationContext import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration import org.springframework.core.io.ClassPathResource -import org.springframework.http.client.reactive.ClientHttpConnector -import org.springframework.http.server.reactive.ServerHttpRequestDecorator -import org.springframework.http.server.reactive.SslInfo -import org.springframework.lang.Nullable import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.config.test.SpringTestContext import org.springframework.security.config.test.SpringTestContextExtension @@ -41,19 +32,15 @@ import org.springframework.security.core.userdetails.User import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor import org.springframework.security.web.server.SecurityWebFilterChain import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager -import org.springframework.test.web.reactive.server.MockServerConfigurer +import org.springframework.test.web.reactive.server.UserWebTestClientConfigurer.x509 import org.springframework.test.web.reactive.server.WebTestClient -import org.springframework.test.web.reactive.server.WebTestClientConfigurer import org.springframework.test.web.reactive.server.expectBody import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RestController import org.springframework.web.reactive.config.EnableWebFlux -import org.springframework.web.server.ServerWebExchange -import org.springframework.web.server.ServerWebExchangeDecorator -import org.springframework.web.server.WebFilter -import org.springframework.web.server.WebFilterChain -import org.springframework.web.server.adapter.WebHttpHandlerBuilder -import reactor.core.publisher.Mono +import java.security.cert.Certificate +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate /** * Tests for [ServerX509Dsl] @@ -83,7 +70,7 @@ class ServerX509DslTests { val certificate = loadCert("rod.cer") this.client - .mutateWith(mockX509(certificate)) + .mutateWith(x509(certificate)) .get() .uri("/username") .exchange() @@ -111,7 +98,7 @@ class ServerX509DslTests { val certificate = loadCert("rodatexampledotcom.cer") this.client - .mutateWith(mockX509(certificate)) + .mutateWith(x509(certificate)) .get() .uri("/username") .exchange() @@ -143,7 +130,7 @@ class ServerX509DslTests { val certificate = loadCert("rod.cer") this.client - .mutateWith(mockX509(certificate)) + .mutateWith(x509(certificate)) .get() .uri("/username") .exchange() @@ -195,43 +182,6 @@ class ServerX509DslTests { } } - private fun mockX509(certificate: X509Certificate): X509Mutator { - return X509Mutator(certificate) - } - - private class X509Mutator internal constructor(private var certificate: X509Certificate) : WebTestClientConfigurer, MockServerConfigurer { - - override fun afterConfigurerAdded(builder: WebTestClient.Builder, - @Nullable httpHandlerBuilder: WebHttpHandlerBuilder?, - @Nullable connector: ClientHttpConnector?) { - val filter = SetSslInfoWebFilter(certificate) - httpHandlerBuilder!!.filters { filters: MutableList -> filters.add(0, filter) } - } - } - - private class SetSslInfoWebFilter(var certificate: X509Certificate) : WebFilter { - - override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { - return chain.filter(decorate(exchange)) - } - - private fun decorate(exchange: ServerWebExchange): ServerWebExchange { - val decorated: ServerHttpRequestDecorator = object : ServerHttpRequestDecorator(exchange.request) { - override fun getSslInfo(): SslInfo { - val sslInfo: SslInfo = mockk() - every { sslInfo.sessionId } returns "sessionId" - every { sslInfo.peerCertificates } returns arrayOf(certificate) - return sslInfo - } - } - return object : ServerWebExchangeDecorator(exchange) { - override fun getRequest(): org.springframework.http.server.reactive.ServerHttpRequest { - return decorated - } - } - } - } - private fun loadCert(location: String): T { ClassPathResource(location).inputStream.use { inputStream -> val certFactory = CertificateFactory.getInstance("X.509") diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index c113bbc609..65e88a720e 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -176,6 +176,7 @@ **** xref:reactive/test/web/authentication.adoc[Testing Authentication] **** xref:reactive/test/web/csrf.adoc[Testing CSRF] **** xref:reactive/test/web/oauth2.adoc[Testing OAuth 2.0] +**** xref:reactive/test/web/x509.adoc[Testing X509] ** xref:reactive/configuration/webflux.adoc[WebFlux Security] * xref:native-image/index.adoc[GraalVM Native Image Support] ** xref:native-image/method-security.adoc[Method Security] diff --git a/docs/modules/ROOT/pages/reactive/test/web/x509.adoc b/docs/modules/ROOT/pages/reactive/test/web/x509.adoc new file mode 100644 index 0000000000..a8f47ee5e5 --- /dev/null +++ b/docs/modules/ROOT/pages/reactive/test/web/x509.adoc @@ -0,0 +1,4 @@ += X509 + +Spring Framework provides first class support for testing X509 with `WebTestClient`. +For details refer to javadoc:{spring-framework-api-url}org.springframework.test.web.reactive.server.UserWebTestClientConfigurer[]. diff --git a/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java index 475787da4f..8c2db3783d 100644 --- a/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java +++ b/docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java @@ -21,31 +21,20 @@ import java.security.cert.Certificate; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import org.jetbrains.annotations.NotNull; -import org.jspecify.annotations.Nullable; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import reactor.core.publisher.Mono; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; -import org.springframework.http.client.reactive.ClientHttpConnector; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.SslInfo; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; import org.springframework.security.test.web.reactive.server.WebTestClientBuilder; import org.springframework.security.web.authentication.preauth.x509.X509TestUtils; import org.springframework.test.web.reactive.server.WebTestClient; -import org.springframework.test.web.reactive.server.WebTestClientConfigurer; -import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; -import org.springframework.web.server.WebFilterChain; -import org.springframework.web.server.adapter.WebHttpHandlerBuilder; import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity; -import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.reactive.server.UserWebTestClientConfigurer.x509; /** * Tests {@link CustomX509Configuration}. @@ -96,46 +85,6 @@ public class X509ConfigurationTests { // @formatter:on } - private static @NotNull WebTestClientConfigurer x509(X509Certificate certificate) { - return (builder, httpHandlerBuilder, connector) -> { - builder.apply(new WebTestClientConfigurer() { - @Override - public void afterConfigurerAdded(WebTestClient.Builder builder, - @Nullable WebHttpHandlerBuilder httpHandlerBuilder, - @Nullable ClientHttpConnector connector) { - SslInfo sslInfo = new SslInfo() { - @Override - public @Nullable String getSessionId() { - return "sessionId"; - } - - @Override - public X509Certificate @Nullable [] getPeerCertificates() { - return new X509Certificate[] { certificate }; - } - }; - httpHandlerBuilder.filters((filters) -> filters.add(0, new SslInfoOverrideWebFilter(sslInfo))); - } - }); - }; - } - - private static class SslInfoOverrideWebFilter implements WebFilter { - private final SslInfo sslInfo; - - private SslInfoOverrideWebFilter(SslInfo sslInfo) { - this.sslInfo = sslInfo; - } - - @Override - public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { - ServerHttpRequest sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo) - .build(); - ServerWebExchange sslInfoExchange = exchange.mutate().request(sslInfoRequest).build(); - return chain.filter(sslInfoExchange); - } - } - private T loadCert(String location) { try (InputStream is = new ClassPathResource(location).getInputStream()) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); diff --git a/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt index 3c8f779f2f..df9476e6f4 100644 --- a/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt +++ b/docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt @@ -26,6 +26,7 @@ import org.springframework.security.config.test.SpringTestContextExtension import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers import org.springframework.security.test.web.reactive.server.WebTestClientBuilder.Http200RestController import org.springframework.security.web.authentication.preauth.x509.X509TestUtils +import org.springframework.test.web.reactive.server.UserWebTestClientConfigurer.x509 import org.springframework.test.web.reactive.server.WebTestClient import org.springframework.test.web.reactive.server.WebTestClientConfigurer import org.springframework.util.Assert @@ -87,15 +88,6 @@ class X509ConfigurationTests { // @formatter:on } - private class SslInfoOverrideWebFilter(private val sslInfo: SslInfo) : WebFilter { - override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono { - val sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo) - .build() - val sslInfoExchange = exchange.mutate().request(sslInfoRequest).build() - return chain.filter(sslInfoExchange) - } - } - private fun loadCert(location: String): T { try { ClassPathResource(location).getInputStream().use { `is` -> @@ -106,28 +98,4 @@ class X509ConfigurationTests { throw IllegalArgumentException(ex) } } - - companion object { - private fun x509(certificate: X509Certificate): WebTestClientConfigurer { - return WebTestClientConfigurer { builder: WebTestClient.Builder, httpHandlerBuilder: WebHttpHandlerBuilder?, connector: ClientHttpConnector? -> - - val sslInfo: SslInfo = object : SslInfo { - override fun getSessionId(): String { - return "sessionId" - } - - override fun getPeerCertificates(): Array { - return arrayOf(certificate) - } - } - Assert.notNull(httpHandlerBuilder, "httpHandlerBuilder should not be null") - httpHandlerBuilder!!.filters(Consumer { filters: MutableList -> - filters.add( - 0, - SslInfoOverrideWebFilter(sslInfo) - ) - }) - } - } - } } diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/ServerX509AuthenticationConverterTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/ServerX509AuthenticationConverterTests.java index 86ff9f42e1..444bd84f76 100644 --- a/web/src/test/java/org/springframework/security/web/server/authentication/ServerX509AuthenticationConverterTests.java +++ b/web/src/test/java/org/springframework/security/web/server/authentication/ServerX509AuthenticationConverterTests.java @@ -69,31 +69,11 @@ public class ServerX509AuthenticationConverterTests { @Test public void shouldReturnAuthenticationForValidCertificate() { givenExtractPrincipalWillReturn(); - this.request.sslInfo(new MockSslInfo(this.certificate)); + this.request.sslInfo(SslInfo.from("123", this.certificate)); Authentication authentication = this.converter.convert(MockServerWebExchange.from(this.request.build())) .block(); assertThat(authentication.getName()).isEqualTo("Luke Taylor"); assertThat(authentication.getCredentials()).isEqualTo(this.certificate); } - class MockSslInfo implements SslInfo { - - private final X509Certificate[] peerCertificates; - - MockSslInfo(X509Certificate... peerCertificates) { - this.peerCertificates = peerCertificates; - } - - @Override - public String getSessionId() { - return "mock-session-id"; - } - - @Override - public X509Certificate[] getPeerCertificates() { - return this.peerCertificates; - } - - } - } 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 a6c290fd88..8e821fa792 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 @@ -16,7 +16,6 @@ package org.springframework.security.web.server.csrf; -import java.security.cert.X509Certificate; import java.time.Duration; import java.time.temporal.ChronoUnit; @@ -179,7 +178,7 @@ class CookieServerCsrfTokenRepositoryTests { @Test void saveTokenWhenSslInfoPresentThenSecure() { - this.request.sslInfo(new MockSslInfo()); + this.request.sslInfo(SslInfo.from("sessionId")); MockServerWebExchange exchange = MockServerWebExchange.from(this.request); this.csrfTokenRepository.saveToken(exchange, createToken()).block(); ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName); @@ -239,7 +238,7 @@ class CookieServerCsrfTokenRepositoryTests { @Test void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecure() { MockServerWebExchange exchange = MockServerWebExchange.from(this.request); - this.request.sslInfo(new MockSslInfo()); + this.request.sslInfo(SslInfo.from("sessionId")); this.csrfTokenRepository.setSecure(false); this.csrfTokenRepository.saveToken(exchange, createToken()).block(); ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName); @@ -250,7 +249,7 @@ class CookieServerCsrfTokenRepositoryTests { @Test void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecureUsingCustomizer() { MockServerWebExchange exchange = MockServerWebExchange.from(this.request); - this.request.sslInfo(new MockSslInfo()); + this.request.sslInfo(SslInfo.from("sessionId")); this.csrfTokenRepository.setCookieCustomizer((customizer) -> customizer.secure(false)); this.csrfTokenRepository.saveToken(exchange, createToken()).block(); ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName); @@ -401,18 +400,4 @@ 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[] {}; - } - - } - }