瀏覽代碼

Update x509 Reference

- Use include-code
- Demo how to customize SubjectX500PrincipalExtractor
Rob Winch 2 月之前
父節點
當前提交
e3add59550
共有 17 個文件被更改,包括 1120 次插入116 次删除
  1. 1 0
      docs/antora.yml
  2. 6 88
      docs/modules/ROOT/pages/reactive/authentication/x509.adoc
  3. 18 28
      docs/modules/ROOT/pages/servlet/authentication/x509.adoc
  4. 74 0
      docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/CustomX509Configuration.java
  5. 67 0
      docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/DefaultX509Configuration.java
  6. 148 0
      docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java
  7. 75 0
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.java
  8. 67 0
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.java
  9. 103 0
      docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/X509ConfigurationTests.java
  10. 74 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/CustomX509Configuration.kt
  11. 64 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/DefaultX509Configuration.kt
  12. 131 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt
  13. 69 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/CustomX509Configuration.kt
  14. 64 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/DefaultX509Configuration.kt
  15. 75 0
      docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/X509ConfigurationTests.kt
  16. 45 0
      docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.xml
  17. 39 0
      docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.xml

+ 1 - 0
docs/antora.yml

@@ -18,3 +18,4 @@ asciidoc:
     gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}"
     gh-url: "https://github.com/spring-projects/spring-security/tree/{gh-tag}"
     include-java: 'example$docs-src/test/java/org/springframework/security/docs'
     include-java: 'example$docs-src/test/java/org/springframework/security/docs'
     include-kotlin: 'example$docs-src/test/kotlin/org/springframework/security/kt/docs'
     include-kotlin: 'example$docs-src/test/kotlin/org/springframework/security/kt/docs'
+    include-xml: 'example$docs-src/test/resources/org/springframework/security/docs'

+ 6 - 88
docs/modules/ROOT/pages/reactive/authentication/x509.adoc

@@ -5,98 +5,16 @@ Similar to xref:servlet/authentication/x509.adoc#servlet-x509[Servlet X.509 auth
 
 
 The following example shows a reactive x509 security configuration:
 The following example shows a reactive x509 security configuration:
 
 
-[tabs]
-======
-Java::
-+
-[source,java,role="primary"]
-----
-@Bean
-public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
-	http
-		.x509(withDefaults())
-		.authorizeExchange(exchanges -> exchanges
-		    .anyExchange().permitAll()
-		);
-	return http.build();
-}
-----
+include-code::./DefaultX509Configuration[tag=springSecurity,indent=0]
 
 
-Kotlin::
-+
-[source,kotlin,role="secondary"]
-----
-@Bean
-fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
-    return http {
-        x509 { }
-        authorizeExchange {
-            authorize(anyExchange, authenticated)
-        }
-    }
-}
-----
-======
-
-In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used. The default principal extractor is `SubjectDnX509PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client. The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
+In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used.
+The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client.
+The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
 
 
 The following example demonstrates how these defaults can be overridden:
 The following example demonstrates how these defaults can be overridden:
 
 
-[tabs]
-======
-Java::
-+
-[source,java,role="primary"]
-----
-@Bean
-public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
-	SubjectDnX509PrincipalExtractor principalExtractor =
-	        new SubjectDnX509PrincipalExtractor();
-
-	principalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)");
-
-	ReactiveAuthenticationManager authenticationManager = authentication -> {
-		authentication.setAuthenticated("Trusted Org Unit".equals(authentication.getName()));
-		return Mono.just(authentication);
-	};
-
-	http
-		.x509(x509 -> x509
-		    .principalExtractor(principalExtractor)
-		    .authenticationManager(authenticationManager)
-		)
-		.authorizeExchange(exchanges -> exchanges
-		    .anyExchange().authenticated()
-		);
-	return http.build();
-}
-----
-
-Kotlin::
-+
-[source,kotlin,role="secondary"]
-----
-@Bean
-fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? {
-    val customPrincipalExtractor = SubjectDnX509PrincipalExtractor()
-    customPrincipalExtractor.setSubjectDnRegex("OU=(.*?)(?:,|$)")
-    val customAuthenticationManager = ReactiveAuthenticationManager { authentication: Authentication ->
-        authentication.isAuthenticated = "Trusted Org Unit" == authentication.name
-        Mono.just(authentication)
-    }
-    return http {
-        x509 {
-            principalExtractor = customPrincipalExtractor
-            authenticationManager = customAuthenticationManager
-        }
-        authorizeExchange {
-            authorize(anyExchange, authenticated)
-        }
-    }
-}
-----
-======
+include-code::./CustomX509Configuration[tag=springSecurity,indent=0]
 
 
-In the previous example, a username is extracted from the OU field of a client certificate instead of CN, and account lookup using `ReactiveUserDetailsService` is not performed at all. Instead, if the provided certificate issued to an OU named "`Trusted Org Unit`", a request is authenticated.
+In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance.
 
 
 For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.
 For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.

+ 18 - 28
docs/modules/ROOT/pages/servlet/authentication/x509.adoc

@@ -14,37 +14,27 @@ You should get this working before trying it out with Spring Security.
 The Spring Security X.509 module extracts the certificate by using a filter.
 The Spring Security X.509 module extracts the certificate by using a filter.
 It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure.
 It maps the certificate to an application user and loads that user's set of granted authorities for use with the standard Spring Security infrastructure.
 
 
-
+[[servlet-x509-config]]
 == Adding X.509 Authentication to Your Web Application
 == Adding X.509 Authentication to Your Web Application
-Enabling X.509 client authentication is very straightforward.
-To do so, add the `<x509/>` element to your http security namespace configuration:
 
 
-[source,xml]
-----
-<http>
-...
-	<x509 subject-principal-regex="CN=(.*?)," user-service-ref="userService"/>;
-</http>
-----
+Similar to xref:reactive/authentication/x509.adoc[Reactive X.509 authentication], the servlet x509 authentication filter allows extracting an authentication token from a certificate provided by a client.
+
+The following example shows a reactive x509 security configuration:
+
+include-code::./DefaultX509Configuration[tag=springSecurity,indent=0]
+
+In the preceding configuration, when neither `principalExtractor` nor `authenticationManager` is provided, defaults are used.
+The default principal extractor is `SubjectX500PrincipalExtractor`, which extracts the CN (common name) field from a certificate provided by a client.
+The default authentication manager is `ReactivePreAuthenticatedAuthenticationManager`, which performs user account validation, checking that a user account with a name extracted by `principalExtractor` exists and that it is not locked, disabled, or expired.
+
+The following example demonstrates how these defaults can be overridden:
+
+include-code::./CustomX509Configuration[tag=springSecurity,indent=0]
+
+In the previous example, a username is extracted from the `emailAddress` field of a client certificate instead of CN, and account lookup uses a custom `ReactiveAuthenticationManager` instance.
+
+For an example of configuring Netty and `WebClient` or `curl` command-line tool to use mutual TLS and enable X.509 authentication, see https://github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.
 
 
-The element has two optional attributes:
-
-* `subject-principal-regex`.
-The regular expression used to extract a username from the certificate's subject name.
-The default value is shown in the preceding listing.
-This is the username that is passed to the `UserDetailsService` to load the authorities for the user.
-* `user-service-ref`.
-This is the bean ID of the `UserDetailsService` to be used with X.509.
-It is not needed if there is only one defined in your application context.
-
-The `subject-principal-regex` should contain a single group.
-For example, the default expression (`CN=(.*?)`) matches the common name field.
-So, if the subject name in the certificate is "CN=Jimi Hendrix, OU=...", this gives a user name of "Jimi Hendrix".
-The matches are case insensitive.
-So "emailAddress=(+.*?+)," matches "EMAILADDRESS=jimi@hendrix.org,CN=...", giving a user name "jimi@hendrix.org".
-If the client presents a certificate and a valid username is successfully extracted, there should be a valid `Authentication` object in the security context.
-If no certificate is found or no corresponding user could be found, the security context remains empty.
-This means that you can use X.509 authentication with other options, such as a form-based login.
 
 
 [[x509-ssl-config]]
 [[x509-ssl-config]]
 == Setting up SSL in Tomcat
 == Setting up SSL in Tomcat

+ 74 - 0
docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/CustomX509Configuration.java

@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.docs.reactive.authentication.reactivex509;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.ReactiveAuthenticationManager;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
+import org.springframework.web.reactive.config.EnableWebFlux;
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableWebFluxSecurity
+@EnableWebFlux
+public class CustomX509Configuration {
+
+	// tag::springSecurity[]
+	@Bean
+	SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
+		SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
+		principalExtractor.setExtractPrincipalNameFromEmail(true);
+
+		// @formatter:off
+		UserDetails user = User
+			.withUsername("luke@monkeymachine")
+			.password("password")
+			.roles("USER")
+			.build();
+		// @formatter:on
+
+		ReactiveUserDetailsService users = new MapReactiveUserDetailsService(user);
+		ReactiveAuthenticationManager authenticationManager = new ReactivePreAuthenticatedAuthenticationManager(users);
+
+		// @formatter:off
+		http
+			.x509(x509 -> x509
+				.principalExtractor(principalExtractor)
+				.authenticationManager(authenticationManager)
+			)
+			.authorizeExchange(exchanges -> exchanges
+				.anyExchange().authenticated()
+			);
+		// @formatter:on
+		return http.build();
+	}
+	// end::springSecurity[]
+
+}

+ 67 - 0
docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/DefaultX509Configuration.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.docs.reactive.authentication.reactivex509;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
+import org.springframework.security.config.web.server.ServerHttpSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.server.SecurityWebFilterChain;
+import org.springframework.web.reactive.config.EnableWebFlux;
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@Configuration(proxyBeanMethods = false)
+@EnableWebFluxSecurity
+@EnableWebFlux
+public class DefaultX509Configuration {
+
+	// tag::springSecurity[]
+	@Bean
+	SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
+		// @formatter:off
+		http
+			.x509(Customizer.withDefaults())
+			.authorizeExchange(exchanges -> exchanges
+				.anyExchange().authenticated()
+			);
+		// @formatter:on
+		return http.build();
+	}
+	// end::springSecurity[]
+
+	@Bean
+	ReactiveUserDetailsService userDetailsService() {
+		// @formatter:off
+		UserDetails user = User
+				.withUsername("rod")
+				.password("password")
+				.roles("USER")
+				.build();
+		// @formatter:on
+
+		return new MapReactiveUserDetailsService(user);
+	}
+}

+ 148 - 0
docs/src/test/java/org/springframework/security/docs/reactive/authentication/reactivex509/X509ConfigurationTests.java

@@ -0,0 +1,148 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.docs.reactive.authentication.reactivex509;
+
+import java.io.InputStream;
+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;
+
+/**
+ * Tests {@link CustomX509Configuration}.
+ *
+ * @author Rob Winch
+ */
+@ExtendWith(SpringTestContextExtension.class)
+public class X509ConfigurationTests {
+
+	public final SpringTestContext spring = new SpringTestContext(this);
+
+	WebTestClient client;
+
+	@Autowired
+	void setSpringSecurityFilterChain(WebFilter springSecurityFilterChain) {
+		this.client = WebTestClient.bindToController(WebTestClientBuilder.Http200RestController.class)
+			.webFilter(springSecurityFilterChain)
+			.apply(springSecurity())
+			.configureClient()
+			.build();
+	}
+
+	@Test
+	void x509WhenDefaultX509Configuration() throws Exception {
+		this.spring.register(DefaultX509Configuration.class).autowire();
+		X509Certificate certificate = loadCert("rod.cer");
+		// @formatter:off
+		this.client
+			.mutateWith(x509(certificate))
+			.get()
+			.uri("/")
+			.exchange()
+			.expectStatus().isOk();
+		// @formatter:on
+	}
+
+	@Test
+	void x509WhenCustomX509Configuration() throws Exception {
+		this.spring.register(CustomX509Configuration.class).autowire();
+		X509Certificate certificate = X509TestUtils.buildTestCertificate();
+		// @formatter:off
+		this.client
+				.mutateWith(x509(certificate))
+				.get()
+				.uri("/")
+				.exchange()
+				.expectStatus().isOk();
+		// @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");
+			return (T) certFactory.generateCertificate(is);
+		}
+		catch (Exception ex) {
+			throw new IllegalArgumentException(ex);
+		}
+	}
+}

+ 75 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.java

@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.docs.servlet.authentication.servletx509config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@EnableWebMvc
+@EnableWebSecurity
+@Configuration(proxyBeanMethods = false)
+public class CustomX509Configuration {
+
+	// tag::springSecurity[]
+	@Bean
+	DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
+		SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
+		principalExtractor.setExtractPrincipalNameFromEmail(true);
+
+
+		// @formatter:off
+		http
+			.x509((x509) -> x509
+				.x509PrincipalExtractor(principalExtractor)
+			)
+			.authorizeHttpRequests((exchanges) -> exchanges
+				.anyRequest().authenticated()
+			);
+		// @formatter:on
+		return http.build();
+	}
+	// end::springSecurity[]
+
+	@Bean
+	UserDetailsService userDetailsService() {
+		// @formatter:off
+		UserDetails user = User
+				.withUsername("luke@monkeymachine")
+				.password("password")
+				.roles("USER")
+				.build();
+		// @formatter:on
+
+		return new InMemoryUserDetailsManager(user);
+	}
+}

+ 67 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.java

@@ -0,0 +1,67 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.docs.servlet.authentication.servletx509config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.provisioning.InMemoryUserDetailsManager;
+import org.springframework.security.web.DefaultSecurityFilterChain;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@EnableWebMvc
+@EnableWebSecurity
+@Configuration(proxyBeanMethods = false)
+public class DefaultX509Configuration {
+
+	// tag::springSecurity[]
+	@Bean
+	DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
+		// @formatter:off
+		http
+			.x509(Customizer.withDefaults())
+			.authorizeHttpRequests(exchanges -> exchanges
+				.anyRequest().authenticated()
+			);
+		// @formatter:on
+		return http.build();
+	}
+	// end::springSecurity[]
+
+	@Bean
+	UserDetailsService userDetailsService() {
+		// @formatter:off
+		UserDetails user = User
+			.withUsername("rod")
+			.password("password")
+			.roles("USER")
+			.build();
+		// @formatter:on
+
+		return new InMemoryUserDetailsManager(user);
+	}
+}

+ 103 - 0
docs/src/test/java/org/springframework/security/docs/servlet/authentication/servletx509config/X509ConfigurationTests.java

@@ -0,0 +1,103 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.docs.servlet.authentication.servletx509config;
+
+import java.io.InputStream;
+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.web.authentication.preauth.x509.X509TestUtils;
+import org.springframework.test.web.reactive.server.WebTestClient;
+import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+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.servlet.request.SecurityMockMvcRequestPostProcessors.x509;
+import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+/**
+ * Tests {@link CustomX509Configuration}.
+ *
+ * @author Rob Winch
+ */
+@ExtendWith(SpringTestContextExtension.class)
+public class X509ConfigurationTests {
+
+	public final SpringTestContext spring = new SpringTestContext(this);
+
+	@Autowired
+	MockMvc mockMvc;
+
+	@Test
+	void x509WhenDefaultX509Configuration() throws Exception {
+		this.spring.register(DefaultX509Configuration.class, Http200Controller.class).autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/").with(x509("rod.cer")))
+			.andExpect(status().isOk())
+			.andExpect(authenticated().withUsername("rod"));
+		// @formatter:on
+	}
+
+	@Test
+	void x509WhenDefaultX509ConfigurationXml() throws Exception {
+		this.spring.testConfigLocations("DefaultX509Configuration.xml").autowire();
+		// @formatter:off
+		this.mockMvc.perform(get("/").with(x509("rod.cer")))
+			.andExpect(authenticated().withUsername("rod"));
+		// @formatter:on
+	}
+
+	@Test
+	void x509WhenCustomX509Configuration() throws Exception {
+		this.spring.register(CustomX509Configuration.class, Http200Controller.class).autowire();
+		X509Certificate certificate = X509TestUtils.buildTestCertificate();
+		// @formatter:off
+		this.mockMvc.perform(get("/").with(x509(certificate)))
+				.andExpect(status().isOk())
+				.andExpect(authenticated().withUsername("luke@monkeymachine"));
+		// @formatter:on
+	}
+
+	@RestController
+	static class Http200Controller {
+		@GetMapping("/**")
+		String ok() {
+			return "ok";
+		}
+	}
+}

+ 74 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/CustomX509Configuration.kt

@@ -0,0 +1,74 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.kt.docs.reactive.authentication.reactivex509
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.authentication.ReactiveAuthenticationManager
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.web.server.invoke
+import org.springframework.security.config.web.server.ServerHttpSecurity
+import org.springframework.security.config.web.server.ServerHttpSecurity.http
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.ReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@EnableWebFlux
+@EnableWebFluxSecurity
+@Configuration(proxyBeanMethods = false)
+class CustomX509Configuration {
+
+    // tag::springSecurity[]
+    @Bean
+    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+        val extractor = SubjectX500PrincipalExtractor()
+        extractor.setExtractPrincipalNameFromEmail(true)
+
+        // @formatter:off
+        val user = User
+            .withUsername("luke@monkeymachine")
+            .password("password")
+            .roles("USER")
+            .build()
+        // @formatter:on
+
+        val users: ReactiveUserDetailsService = MapReactiveUserDetailsService(user)
+        val authentication: ReactiveAuthenticationManager = ReactivePreAuthenticatedAuthenticationManager(users)
+
+        return http {
+            x509 {
+                principalExtractor = extractor
+                authenticationManager = authentication
+            }
+            authorizeExchange {
+                authorize(anyExchange, authenticated)
+            }
+        }
+    }
+    // end::springSecurity[]
+
+
+}

+ 64 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/DefaultX509Configuration.kt

@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.security.kt.docs.reactive.authentication.reactivex509
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity
+import org.springframework.security.config.web.server.ServerHttpSecurity
+import org.springframework.security.config.web.server.invoke
+import org.springframework.security.core.userdetails.MapReactiveUserDetailsService
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.web.server.SecurityWebFilterChain
+import org.springframework.web.reactive.config.EnableWebFlux
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@EnableWebFlux
+@EnableWebFluxSecurity
+@Configuration(proxyBeanMethods = false)
+class DefaultX509Configuration {
+
+    // tag::springSecurity[]
+    @Bean
+    fun securityWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
+        return http {
+            x509 { }
+            authorizeExchange {
+                authorize(anyExchange, authenticated)
+            }
+        }
+    }
+    // end::springSecurity[]
+
+    @Bean
+    fun userDetailsService(): MapReactiveUserDetailsService {
+        // @formatter:off
+        val user = User
+            .withUsername("rod")
+            .password("password")
+            .roles("USER")
+            .build()
+        // @formatter:on
+
+        return MapReactiveUserDetailsService(user)
+    }
+
+}

+ 131 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/reactive/authentication/reactivex509/X509ConfigurationTests.kt

@@ -0,0 +1,131 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kt.docs.reactive.authentication.reactivex509
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+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.SslInfo
+import org.springframework.security.config.test.SpringTestContext
+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.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 reactor.core.publisher.Mono
+import java.security.cert.Certificate
+import java.security.cert.CertificateFactory
+import java.security.cert.X509Certificate
+import java.util.function.Consumer
+
+/**
+ * Tests [CustomX509Configuration].
+ *
+ * @author Rob Winch
+ */
+@ExtendWith(SpringTestContextExtension::class)
+class X509ConfigurationTests {
+    @JvmField
+    val spring: SpringTestContext = SpringTestContext(this)
+
+    var client: WebTestClient? = null
+
+    @Autowired
+    fun setSpringSecurityFilterChain(springSecurityFilterChain: WebFilter) {
+        this.client = WebTestClient
+            .bindToController(Http200RestController::class.java)
+            .webFilter<WebTestClient.ControllerSpec>(springSecurityFilterChain)
+            .apply<WebTestClient.ControllerSpec>(SecurityMockServerConfigurers.springSecurity())
+            .configureClient()
+            .build()
+    }
+
+    @Test
+    fun x509WhenDefaultX509Configuration() {
+        this.spring.register(DefaultX509Configuration::class.java).autowire()
+        val certificate = loadCert<X509Certificate>("rod.cer")
+        // @formatter:off
+        this.client!!.mutateWith(x509(certificate))
+            .get()
+            .uri("/")
+            .exchange()
+            .expectStatus().isOk()
+        // @formatter:on
+    }
+
+    @Test
+    fun x509WhenCustomX509Configuration() {
+        this.spring.register(CustomX509Configuration::class.java).autowire()
+        val certificate = X509TestUtils.buildTestCertificate()
+        // @formatter:off
+        this.client!!.mutateWith(x509(certificate))
+            .get()
+            .uri("/")
+            .exchange()
+            .expectStatus().isOk()
+        // @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` ->
+                val certFactory = CertificateFactory.getInstance("X.509")
+                return certFactory.generateCertificate(`is`) as T
+            }
+        } catch (ex: Exception) {
+            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)
+                    }
+                }
+                httpHandlerBuilder.filters(Consumer { filters: MutableList<WebFilter> ->
+                    filters.add(
+                        0,
+                        SslInfoOverrideWebFilter(sslInfo)
+                    )
+                })
+            }
+        }
+    }
+}

+ 69 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/CustomX509Configuration.kt

@@ -0,0 +1,69 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kt.docs.servlet.authentication.servlet509config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.invoke
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.web.DefaultSecurityFilterChain
+import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@EnableWebMvc
+@EnableWebSecurity
+@Configuration(proxyBeanMethods = false)
+class CustomX509Configuration {
+    // tag::springSecurity[]
+    @Bean
+    fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? {
+        val principalExtractor = SubjectX500PrincipalExtractor()
+        principalExtractor.setExtractPrincipalNameFromEmail(true)
+
+        // @formatter:off
+        http {
+            authorizeHttpRequests {
+                authorize(anyRequest, authenticated)
+            }
+            x509 {
+                x509PrincipalExtractor = principalExtractor
+            }
+        }
+        return http.build()
+    }
+    // end::springSecurity[]
+
+    @Bean
+    fun userDetailsService(): UserDetailsService {
+        // @formatter:off
+        val user = User
+            .withUsername("luke@monkeymachine")
+            .password("password")
+            .roles("USER")
+            .build()
+        // @formatter:on
+        return InMemoryUserDetailsManager(user)
+    }
+}

+ 64 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/DefaultX509Configuration.kt

@@ -0,0 +1,64 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kt.docs.servlet.authentication.servlet509config
+
+import org.springframework.context.annotation.Bean
+import org.springframework.context.annotation.Configuration
+import org.springframework.security.config.annotation.web.builders.HttpSecurity
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
+import org.springframework.security.config.annotation.web.invoke
+import org.springframework.security.core.userdetails.User
+import org.springframework.security.core.userdetails.UserDetailsService
+import org.springframework.security.provisioning.InMemoryUserDetailsManager
+import org.springframework.security.web.DefaultSecurityFilterChain
+import org.springframework.web.servlet.config.annotation.EnableWebMvc
+
+/**
+ * Demonstrates custom configuration for x509 reactive configuration.
+ *
+ * @author Rob Winch
+ */
+@EnableWebMvc
+@EnableWebSecurity
+@Configuration(proxyBeanMethods = false)
+class DefaultX509Configuration {
+    // tag::springSecurity[]
+    @Bean
+    fun springSecurity(http: HttpSecurity): DefaultSecurityFilterChain? {
+        // @formatter:off
+        http {
+            authorizeHttpRequests {
+                authorize(anyRequest, authenticated)
+            }
+            x509 { }
+        }
+        // @formatter:on
+        return http.build()
+    }
+    // end::springSecurity[]
+
+    @Bean
+    fun userDetailsService(): UserDetailsService {
+        // @formatter:off
+        val user = User
+            .withUsername("rod")
+            .password("password")
+            .roles("USER")
+            .build()
+        // @formatter:on
+        return InMemoryUserDetailsManager(user)
+    }
+}

+ 75 - 0
docs/src/test/kotlin/org/springframework/security/kt/docs/servlet/authentication/servletx509config/X509ConfigurationTests.kt

@@ -0,0 +1,75 @@
+/*
+ * Copyright 2002-2025 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.security.kt.docs.servlet.authentication.servlet509config
+
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.ExtendWith
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.config.test.SpringTestContext
+import org.springframework.security.config.test.SpringTestContextExtension
+import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors
+import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
+import org.springframework.security.web.authentication.preauth.x509.X509TestUtils
+import org.springframework.test.web.servlet.MockMvc
+import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
+import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.bind.annotation.RestController
+
+/**
+ * Tests [CustomX509Configuration].
+ *
+ * @author Rob Winch
+ */
+@ExtendWith(SpringTestContextExtension::class)
+class X509ConfigurationTests {
+    @JvmField
+    val spring: SpringTestContext = SpringTestContext(this)
+
+    @Autowired
+    var mockMvc: MockMvc? = null
+
+    @Test
+    @Throws(Exception::class)
+    fun x509WhenDefaultX509Configuration() {
+        this.spring.register(DefaultX509Configuration::class.java, Http200Controller::class.java).autowire()
+        // @formatter:off
+        this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509("rod.cer")))
+            .andExpect(status().isOk())
+            .andExpect(authenticated().withUsername("rod"))
+        // @formatter:on
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun x509WhenCustomX509Configuration() {
+        this.spring.register(CustomX509Configuration::class.java, Http200Controller::class.java).autowire()
+        val certificate = X509TestUtils.buildTestCertificate()
+        // @formatter:off
+        this.mockMvc!!.perform(get("/").with(SecurityMockMvcRequestPostProcessors.x509(certificate)))
+        .andExpect(status().isOk())
+        .andExpect(authenticated().withUsername("luke@monkeymachine"))
+    		// @formatter:on
+    }
+
+    @RestController
+    internal class Http200Controller {
+        @GetMapping("/**")
+        fun ok(): String {
+            return "ok"
+        }
+    }
+}

+ 45 - 0
docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/CustomX509Configuration.xml

@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2018 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~       https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+         xmlns:p="http://www.springframework.org/schema/p"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://www.springframework.org/schema/security"
+         xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+    <!-- tag::springSecurity[] -->
+    <http>
+        <intercept-url pattern="/**" access="authenticated"/>
+        <x509 principal-extractor-ref="principalExtractor"/>
+    </http>
+    <b:bean id="principalExtractor"
+        class="org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor"
+        p:extractPrincipalNameFromEmail="true"/>
+    <!-- end::springSecurity[] -->
+
+    <user-service id="us">
+        <user name="luke@monkeymachine" password="{noop}password" authorities="ROLE_USER"/>
+    </user-service>
+
+
+
+    <b:import resource="MiscHttpConfigTests-controllers.xml"/>
+</b:beans>

+ 39 - 0
docs/src/test/resources/org/springframework/security/docs/servlet/authentication/servletx509config/DefaultX509Configuration.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright 2002-2018 the original author or authors.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~       https://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<b:beans xmlns:b="http://www.springframework.org/schema/beans"
+         xmlns:p="http://www.springframework.org/schema/p"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xmlns="http://www.springframework.org/schema/security"
+         xsi:schemaLocation="
+			http://www.springframework.org/schema/security
+			https://www.springframework.org/schema/security/spring-security.xsd
+			http://www.springframework.org/schema/beans
+			https://www.springframework.org/schema/beans/spring-beans.xsd">
+
+    <!-- tag::springSecurity[] -->
+    <http>
+        <intercept-url pattern="/**" access="authenticated"/>
+        <x509 />
+    </http>
+    <!-- end::springSecurity[] -->
+
+    <user-service>
+        <user name="rod" password="{noop}password" authorities="ROLE_USER"/>
+    </user-service>
+
+</b:beans>