WebFlux中如何实现等效于Servlet的.x509().subjectPrincipalRegex(...)证书认证?
.x509().subjectPrincipalRegex(...) for Client Certificate Authentication Great question! In Spring WebFlux's reactive security stack, there’s no direct out-of-the-box .x509() method like in the Servlet-based HttpSecurity. But we can build an equivalent solution to extract the client certificate’s subject as a username and feed it into your ReactiveUserDetailsService using custom components. Here’s how to do it step by step:
Step 1: Configure SSL to Require Client Certificates
First, ensure your server is set up to request and validate client certificates. Add this to your application.yml (or .properties):
server: ssl: enabled: true key-store: classpath:server.jks key-store-password: your-server-keystore-pass trust-store: classpath:truststore.jks trust-store-password: your-truststore-pass client-auth: required # Forces clients to present a valid certificate
Step 2: Create a Custom X509 Authentication Converter
This component will extract the client certificate from the request, parse its subject DN using your custom regex (mirroring subjectPrincipalRegex), and convert it into an authentication token.
import org.springframework.security.core.Authentication; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import javax.net.ssl.SSLSession; import java.security.cert.X509Certificate; public class X509ServerAuthenticationConverter implements ServerAuthenticationConverter { // Match this to your desired subject regex (same as .subjectPrincipalRegex() in Servlet) private String subjectPrincipalRegex = "CN=(.*?)(?:,|$)"; @Override public Mono<Authentication> convert(ServerWebExchange exchange) { return Mono.fromCallable(() -> exchange.getRequest().getSslInfo()) .filter(sslInfo -> sslInfo.getSession() != null) .map(sslInfo -> sslInfo.getSession().getPeerCertificates()) .filter(certs -> certs.length > 0 && certs[0] instanceof X509Certificate) .map(certs -> (X509Certificate) certs[0]) .map(this::extractUsernameFromSubjectDn) .map(username -> new UsernamePasswordAuthenticationToken(username, null, null)); } private String extractUsernameFromSubjectDn(X509Certificate cert) { String subjectDn = cert.getSubjectDN().getName(); java.util.regex.Matcher matcher = java.util.regex.Pattern.compile(subjectPrincipalRegex).matcher(subjectDn); return matcher.find() ? matcher.group(1) : subjectDn; } // Setter to customize the regex externally public void setSubjectPrincipalRegex(String subjectPrincipalRegex) { this.subjectPrincipalRegex = subjectPrincipalRegex; } }
Step 3: Integrate with ServerHttpSecurity
Wire up the custom converter into your security filter chain, and link it to your ReactiveUserDetailsService via an authentication manager:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.web.server.ServerHttpSecurity; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; @Configuration @EnableWebFluxSecurity public class WebFluxSecurityConfig { @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, ReactiveUserDetailsService userDetailsService) { // Create an authentication manager that uses your ReactiveUserDetailsService ReactiveAuthenticationManager authManager = new UserDetailsRepositoryReactiveAuthenticationManager(userDetailsService); // Build the X509 authentication filter with our custom converter AuthenticationWebFilter x509AuthFilter = new AuthenticationWebFilter(authManager); X509ServerAuthenticationConverter x509Converter = new X509ServerAuthenticationConverter(); // Set your custom regex here (e.g., to extract UID instead of CN) x509Converter.setSubjectPrincipalRegex("UID=(.*?)(?:,|$)"); x509AuthFilter.setServerAuthenticationConverter(x509Converter); // Configure the security chain return http .csrf(ServerHttpSecurity.CsrfSpec::disable) .authorizeExchange(exchanges -> exchanges .anyExchange().authenticated()) .addFilterAt(x509AuthFilter, SecurityWebFiltersOrder.AUTHENTICATION) .build(); } }
Key Notes
- Regex Flexibility: Just like
.subjectPrincipalRegex(), you can adjust the regex to extract any part of the certificate’s subject DN (e.g., CN, UID, email) based on your needs. - Alternative to Regex: For more robust parsing, you can use
javax.security.auth.x500.X500Principal's methods to extract specific attributes instead of regex, avoiding pattern matching errors. - Authentication Flow: The filter will extract the username from the certificate, pass it to
ReactiveUserDetailsServiceto load user details, and validate the user’s existence/authorities just like standard authentication.
内容的提问来源于stack exchange,提问作者Stmated

