Skip to content

Commit e48fdd5

Browse files
committed
Use UserWebTestClientConfigurer
Closes gh-17496
1 parent dbb3b7e commit e48fdd5

File tree

7 files changed

+18
-181
lines changed

7 files changed

+18
-181
lines changed

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

Lines changed: 7 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,13 @@
1616

1717
package org.springframework.security.config.web.server
1818

19-
import io.mockk.every
20-
import io.mockk.mockk
21-
import java.security.cert.Certificate
22-
import java.security.cert.CertificateFactory
23-
import java.security.cert.X509Certificate
2419
import org.junit.jupiter.api.Test
2520
import org.junit.jupiter.api.extension.ExtendWith
2621
import org.springframework.beans.factory.annotation.Autowired
2722
import org.springframework.context.ApplicationContext
2823
import org.springframework.context.annotation.Bean
2924
import org.springframework.context.annotation.Configuration
3025
import org.springframework.core.io.ClassPathResource
31-
import org.springframework.http.client.reactive.ClientHttpConnector
32-
import org.springframework.http.server.reactive.ServerHttpRequestDecorator
33-
import org.springframework.http.server.reactive.SslInfo
34-
import org.springframework.lang.Nullable
3526
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
3627
import org.springframework.security.config.test.SpringTestContext
3728
import org.springframework.security.config.test.SpringTestContextExtension
@@ -41,19 +32,15 @@ import org.springframework.security.core.userdetails.User
4132
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor
4233
import org.springframework.security.web.server.SecurityWebFilterChain
4334
import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager
44-
import org.springframework.test.web.reactive.server.MockServerConfigurer
35+
import org.springframework.test.web.reactive.server.UserWebTestClientConfigurer.x509
4536
import org.springframework.test.web.reactive.server.WebTestClient
46-
import org.springframework.test.web.reactive.server.WebTestClientConfigurer
4737
import org.springframework.test.web.reactive.server.expectBody
4838
import org.springframework.web.bind.annotation.GetMapping
4939
import org.springframework.web.bind.annotation.RestController
5040
import org.springframework.web.reactive.config.EnableWebFlux
51-
import org.springframework.web.server.ServerWebExchange
52-
import org.springframework.web.server.ServerWebExchangeDecorator
53-
import org.springframework.web.server.WebFilter
54-
import org.springframework.web.server.WebFilterChain
55-
import org.springframework.web.server.adapter.WebHttpHandlerBuilder
56-
import reactor.core.publisher.Mono
41+
import java.security.cert.Certificate
42+
import java.security.cert.CertificateFactory
43+
import java.security.cert.X509Certificate
5744

5845
/**
5946
* Tests for [ServerX509Dsl]
@@ -83,7 +70,7 @@ class ServerX509DslTests {
8370
val certificate = loadCert<X509Certificate>("rod.cer")
8471

8572
this.client
86-
.mutateWith(mockX509(certificate))
73+
.mutateWith(x509(certificate))
8774
.get()
8875
.uri("/username")
8976
.exchange()
@@ -111,7 +98,7 @@ class ServerX509DslTests {
11198
val certificate = loadCert<X509Certificate>("rodatexampledotcom.cer")
11299

113100
this.client
114-
.mutateWith(mockX509(certificate))
101+
.mutateWith(x509(certificate))
115102
.get()
116103
.uri("/username")
117104
.exchange()
@@ -143,7 +130,7 @@ class ServerX509DslTests {
143130
val certificate = loadCert<X509Certificate>("rod.cer")
144131

145132
this.client
146-
.mutateWith(mockX509(certificate))
133+
.mutateWith(x509(certificate))
147134
.get()
148135
.uri("/username")
149136
.exchange()
@@ -195,43 +182,6 @@ class ServerX509DslTests {
195182
}
196183
}
197184

198-
private fun mockX509(certificate: X509Certificate): X509Mutator {
199-
return X509Mutator(certificate)
200-
}
201-
202-
private class X509Mutator internal constructor(private var certificate: X509Certificate) : WebTestClientConfigurer, MockServerConfigurer {
203-
204-
override fun afterConfigurerAdded(builder: WebTestClient.Builder,
205-
@Nullable httpHandlerBuilder: WebHttpHandlerBuilder?,
206-
@Nullable connector: ClientHttpConnector?) {
207-
val filter = SetSslInfoWebFilter(certificate)
208-
httpHandlerBuilder!!.filters { filters: MutableList<WebFilter> -> filters.add(0, filter) }
209-
}
210-
}
211-
212-
private class SetSslInfoWebFilter(var certificate: X509Certificate) : WebFilter {
213-
214-
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
215-
return chain.filter(decorate(exchange))
216-
}
217-
218-
private fun decorate(exchange: ServerWebExchange): ServerWebExchange {
219-
val decorated: ServerHttpRequestDecorator = object : ServerHttpRequestDecorator(exchange.request) {
220-
override fun getSslInfo(): SslInfo {
221-
val sslInfo: SslInfo = mockk()
222-
every { sslInfo.sessionId } returns "sessionId"
223-
every { sslInfo.peerCertificates } returns arrayOf(certificate)
224-
return sslInfo
225-
}
226-
}
227-
return object : ServerWebExchangeDecorator(exchange) {
228-
override fun getRequest(): org.springframework.http.server.reactive.ServerHttpRequest {
229-
return decorated
230-
}
231-
}
232-
}
233-
}
234-
235185
private fun <T : Certificate> loadCert(location: String): T {
236186
ClassPathResource(location).inputStream.use { inputStream ->
237187
val certFactory = CertificateFactory.getInstance("X.509")

docs/modules/ROOT/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@
176176
**** xref:reactive/test/web/authentication.adoc[Testing Authentication]
177177
**** xref:reactive/test/web/csrf.adoc[Testing CSRF]
178178
**** xref:reactive/test/web/oauth2.adoc[Testing OAuth 2.0]
179+
**** xref:reactive/test/web/x509.adoc[Testing X509]
179180
** xref:reactive/configuration/webflux.adoc[WebFlux Security]
180181
* xref:native-image/index.adoc[GraalVM Native Image Support]
181182
** xref:native-image/method-security.adoc[Method Security]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
= X509
2+
3+
Spring Framework provides first class support for testing X509 with `WebTestClient`.
4+
For details refer to javadoc:{spring-framework-api-url}org.springframework.test.web.reactive.server.UserWebTestClientConfigurer[].

docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -21,31 +21,20 @@
2121
import java.security.cert.CertificateFactory;
2222
import java.security.cert.X509Certificate;
2323

24-
import org.jetbrains.annotations.NotNull;
25-
import org.jspecify.annotations.Nullable;
2624
import org.junit.jupiter.api.Test;
2725
import org.junit.jupiter.api.extension.ExtendWith;
28-
import reactor.core.publisher.Mono;
2926

3027
import org.springframework.beans.factory.annotation.Autowired;
3128
import org.springframework.core.io.ClassPathResource;
32-
import org.springframework.http.client.reactive.ClientHttpConnector;
33-
import org.springframework.http.server.reactive.ServerHttpRequest;
34-
import org.springframework.http.server.reactive.SslInfo;
3529
import org.springframework.security.config.test.SpringTestContext;
3630
import org.springframework.security.config.test.SpringTestContextExtension;
3731
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
3832
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
3933
import org.springframework.test.web.reactive.server.WebTestClient;
40-
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
41-
import org.springframework.web.server.ServerWebExchange;
4234
import org.springframework.web.server.WebFilter;
43-
import org.springframework.web.server.WebFilterChain;
44-
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
4535

4636
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
47-
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
48-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
37+
import static org.springframework.test.web.reactive.server.UserWebTestClientConfigurer.x509;
4938

5039
/**
5140
* Tests {@link CustomX509Configuration}.
@@ -96,46 +85,6 @@ void x509WhenCustomX509Configuration() throws Exception {
9685
// @formatter:on
9786
}
9887

99-
private static @NotNull WebTestClientConfigurer x509(X509Certificate certificate) {
100-
return (builder, httpHandlerBuilder, connector) -> {
101-
builder.apply(new WebTestClientConfigurer() {
102-
@Override
103-
public void afterConfigurerAdded(WebTestClient.Builder builder,
104-
@Nullable WebHttpHandlerBuilder httpHandlerBuilder,
105-
@Nullable ClientHttpConnector connector) {
106-
SslInfo sslInfo = new SslInfo() {
107-
@Override
108-
public @Nullable String getSessionId() {
109-
return "sessionId";
110-
}
111-
112-
@Override
113-
public X509Certificate @Nullable [] getPeerCertificates() {
114-
return new X509Certificate[] { certificate };
115-
}
116-
};
117-
httpHandlerBuilder.filters((filters) -> filters.add(0, new SslInfoOverrideWebFilter(sslInfo)));
118-
}
119-
});
120-
};
121-
}
122-
123-
private static class SslInfoOverrideWebFilter implements WebFilter {
124-
private final SslInfo sslInfo;
125-
126-
private SslInfoOverrideWebFilter(SslInfo sslInfo) {
127-
this.sslInfo = sslInfo;
128-
}
129-
130-
@Override
131-
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
132-
ServerHttpRequest sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo)
133-
.build();
134-
ServerWebExchange sslInfoExchange = exchange.mutate().request(sslInfoRequest).build();
135-
return chain.filter(sslInfoExchange);
136-
}
137-
}
138-
13988
private <T extends Certificate> T loadCert(String location) {
14089
try (InputStream is = new ClassPathResource(location).getInputStream()) {
14190
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import org.springframework.security.config.test.SpringTestContextExtension
2626
import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers
2727
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder.Http200RestController
2828
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils
29+
import org.springframework.test.web.reactive.server.UserWebTestClientConfigurer.x509
2930
import org.springframework.test.web.reactive.server.WebTestClient
3031
import org.springframework.test.web.reactive.server.WebTestClientConfigurer
3132
import org.springframework.util.Assert
@@ -87,15 +88,6 @@ class X509ConfigurationTests {
8788
// @formatter:on
8889
}
8990

90-
private class SslInfoOverrideWebFilter(private val sslInfo: SslInfo) : WebFilter {
91-
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
92-
val sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo)
93-
.build()
94-
val sslInfoExchange = exchange.mutate().request(sslInfoRequest).build()
95-
return chain.filter(sslInfoExchange)
96-
}
97-
}
98-
9991
private fun <T : Certificate?> loadCert(location: String): T {
10092
try {
10193
ClassPathResource(location).getInputStream().use { `is` ->
@@ -106,28 +98,4 @@ class X509ConfigurationTests {
10698
throw IllegalArgumentException(ex)
10799
}
108100
}
109-
110-
companion object {
111-
private fun x509(certificate: X509Certificate): WebTestClientConfigurer {
112-
return WebTestClientConfigurer { builder: WebTestClient.Builder, httpHandlerBuilder: WebHttpHandlerBuilder?, connector: ClientHttpConnector? ->
113-
114-
val sslInfo: SslInfo = object : SslInfo {
115-
override fun getSessionId(): String {
116-
return "sessionId"
117-
}
118-
119-
override fun getPeerCertificates(): Array<X509Certificate> {
120-
return arrayOf(certificate)
121-
}
122-
}
123-
Assert.notNull(httpHandlerBuilder, "httpHandlerBuilder should not be null")
124-
httpHandlerBuilder!!.filters(Consumer { filters: MutableList<WebFilter> ->
125-
filters.add(
126-
0,
127-
SslInfoOverrideWebFilter(sslInfo)
128-
)
129-
})
130-
}
131-
}
132-
}
133101
}

web/src/test/java/org/springframework/security/web/server/authentication/ServerX509AuthenticationConverterTests.java

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -69,31 +69,11 @@ public void shouldReturnNullForInvalidCertificate() {
6969
@Test
7070
public void shouldReturnAuthenticationForValidCertificate() {
7171
givenExtractPrincipalWillReturn();
72-
this.request.sslInfo(new MockSslInfo(this.certificate));
72+
this.request.sslInfo(SslInfo.from("123", this.certificate));
7373
Authentication authentication = this.converter.convert(MockServerWebExchange.from(this.request.build()))
7474
.block();
7575
assertThat(authentication.getName()).isEqualTo("Luke Taylor");
7676
assertThat(authentication.getCredentials()).isEqualTo(this.certificate);
7777
}
7878

79-
class MockSslInfo implements SslInfo {
80-
81-
private final X509Certificate[] peerCertificates;
82-
83-
MockSslInfo(X509Certificate... peerCertificates) {
84-
this.peerCertificates = peerCertificates;
85-
}
86-
87-
@Override
88-
public String getSessionId() {
89-
return "mock-session-id";
90-
}
91-
92-
@Override
93-
public X509Certificate[] getPeerCertificates() {
94-
return this.peerCertificates;
95-
}
96-
97-
}
98-
9979
}

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

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.security.web.server.csrf;
1818

19-
import java.security.cert.X509Certificate;
2019
import java.time.Duration;
2120
import java.time.temporal.ChronoUnit;
2221

@@ -179,7 +178,7 @@ void saveTokenWhenCustomPropertiesThenCustomPropertiesUsingCustomizer() {
179178

180179
@Test
181180
void saveTokenWhenSslInfoPresentThenSecure() {
182-
this.request.sslInfo(new MockSslInfo());
181+
this.request.sslInfo(SslInfo.from("sessionId"));
183182
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
184183
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
185184
ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
@@ -239,7 +238,7 @@ void saveTokenWhenSecureFlagFalseThenNotSecureUsingCustomizer() {
239238
@Test
240239
void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecure() {
241240
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
242-
this.request.sslInfo(new MockSslInfo());
241+
this.request.sslInfo(SslInfo.from("sessionId"));
243242
this.csrfTokenRepository.setSecure(false);
244243
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
245244
ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
@@ -250,7 +249,7 @@ void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecure() {
250249
@Test
251250
void saveTokenWhenSecureFlagFalseAndSslInfoThenNotSecureUsingCustomizer() {
252251
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
253-
this.request.sslInfo(new MockSslInfo());
252+
this.request.sslInfo(SslInfo.from("sessionId"));
254253
this.csrfTokenRepository.setCookieCustomizer((customizer) -> customizer.secure(false));
255254
this.csrfTokenRepository.saveToken(exchange, createToken()).block();
256255
ResponseCookie cookie = exchange.getResponse().getCookies().getFirst(this.expectedCookieName);
@@ -401,18 +400,4 @@ private static CsrfToken createToken(String headerName, String parameterName, St
401400
return new DefaultCsrfToken(headerName, parameterName, tokenValue);
402401
}
403402

404-
static class MockSslInfo implements SslInfo {
405-
406-
@Override
407-
public String getSessionId() {
408-
return "sessionId";
409-
}
410-
411-
@Override
412-
public X509Certificate[] getPeerCertificates() {
413-
return new X509Certificate[] {};
414-
}
415-
416-
}
417-
418403
}

0 commit comments

Comments
 (0)