This version is still in development and is not considered stable yet. For the latest stable version, please use Spring Security 6.3.4!

Reactive X.509 Authentication

Similar to Servlet X.509 authentication, the reactive x509 authentication filter allows extracting an authentication token from a certificate provided by a client.

The following example shows a reactive x509 security configuration:

  • Java

  • Kotlin

@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
	http
		.x509(withDefaults())
		.authorizeExchange(exchanges -> exchanges
		    .anyExchange().permitAll()
		);
	return http.build();
}
@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.

The following example demonstrates how these defaults can be overridden:

  • Java

  • Kotlin

@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();
}
@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)
        }
    }
}

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.

For an example of configuring Netty and WebClient or curl command-line tool to use mutual TLS and enable X.509 authentication, see github.com/spring-projects/spring-security-samples/tree/main/servlet/java-configuration/authentication/x509.