mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-12 05:13:33 +00:00
Use UserWebTestClientConfigurer
Closes gh-17496
This commit is contained in:
parent
dbb3b7e1f5
commit
e48fdd5ed4
@ -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<X509Certificate>("rod.cer")
|
||||
|
||||
this.client
|
||||
.mutateWith(mockX509(certificate))
|
||||
.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/username")
|
||||
.exchange()
|
||||
@ -111,7 +98,7 @@ class ServerX509DslTests {
|
||||
val certificate = loadCert<X509Certificate>("rodatexampledotcom.cer")
|
||||
|
||||
this.client
|
||||
.mutateWith(mockX509(certificate))
|
||||
.mutateWith(x509(certificate))
|
||||
.get()
|
||||
.uri("/username")
|
||||
.exchange()
|
||||
@ -143,7 +130,7 @@ class ServerX509DslTests {
|
||||
val certificate = loadCert<X509Certificate>("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<WebFilter> -> filters.add(0, filter) }
|
||||
}
|
||||
}
|
||||
|
||||
private class SetSslInfoWebFilter(var certificate: X509Certificate) : WebFilter {
|
||||
|
||||
override fun filter(exchange: ServerWebExchange, chain: WebFilterChain): Mono<Void> {
|
||||
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 <T : Certificate> loadCert(location: String): T {
|
||||
ClassPathResource(location).inputStream.use { inputStream ->
|
||||
val certFactory = CertificateFactory.getInstance("X.509")
|
||||
|
@ -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]
|
||||
|
4
docs/modules/ROOT/pages/reactive/test/web/x509.adoc
Normal file
4
docs/modules/ROOT/pages/reactive/test/web/x509.adoc
Normal file
@ -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[].
|
@ -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<Void> 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 extends Certificate> T loadCert(String location) {
|
||||
try (InputStream is = new ClassPathResource(location).getInputStream()) {
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
|
@ -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<Void> {
|
||||
val sslInfoRequest = exchange.getRequest().mutate().sslInfo(sslInfo)
|
||||
.build()
|
||||
val sslInfoExchange = exchange.mutate().request(sslInfoRequest).build()
|
||||
return chain.filter(sslInfoExchange)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T : Certificate?> 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<X509Certificate> {
|
||||
return arrayOf(certificate)
|
||||
}
|
||||
}
|
||||
Assert.notNull(httpHandlerBuilder, "httpHandlerBuilder should not be null")
|
||||
httpHandlerBuilder!!.filters(Consumer { filters: MutableList<WebFilter> ->
|
||||
filters.add(
|
||||
0,
|
||||
SslInfoOverrideWebFilter(sslInfo)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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[] {};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user