如何在Spring WebFlux中实现自定义Spring Security PreAuthentication并完成MVC迁移?
Great question! Migrating pre-authentication from Spring MVC to WebFlux does require shifting from servlet-based filters to reactive web components, but once you get the hang of the core reactive security APIs, it's straightforward. Let's walk through this step by step.
First, forget about Servlet Filters—WebFlux uses ServerWebFilter and reactive-specific security components instead. The core pieces you'll need are:
ServerAuthenticationConverter: Extracts pre-authenticated credentials (like headers, cookies, or request attributes) from aServerWebExchange(the reactive equivalent ofHttpServletRequest).AuthenticationWebFilter: The reactive filter that uses your converter to create anAuthenticationtoken, then delegates to aServerAuthenticationManagerfor validation.ServerAuthenticationManager: Reactive counterpart of the standardAuthenticationManager, responsible for verifying the pre-authenticated token.
Step 1: Build a Custom ServerAuthenticationConverter
This replaces your MVC filter's logic for extracting the principal/credentials. For example, if you were using request headers for pre-auth:
import org.springframework.security.core.Authentication; import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; public class CustomPreAuthConverter implements ServerAuthenticationConverter { @Override public Mono<Authentication> convert(ServerWebExchange exchange) { // Extract your pre-authenticated principal (e.g., from a header) return Mono.justOrEmpty(exchange.getRequest().getHeaders().getFirst("X-PREAUTH-USER-ID")) .map(userId -> { // Create a pre-authenticated token (credentials can be null if not needed) return new PreAuthenticatedAuthenticationToken(userId, null, null); }); } }
Step 2: Configure a Reactive Authentication Manager
You need a ServerAuthenticationManager to validate the token. If you're using a user details service, use ReactiveUserDetailsService for non-blocking lookups:
import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.ReactiveUserDetailsService; import reactor.core.publisher.Mono; public class PreAuthAuthenticationManager implements ReactiveAuthenticationManager { private final ReactiveUserDetailsService userDetailsService; public PreAuthAuthenticationManager(ReactiveUserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } @Override public Mono<Authentication> authenticate(Authentication authentication) { String userId = authentication.getName(); return userDetailsService.findByUsername(userId) .map(userDetails -> new UsernamePasswordAuthenticationToken( userDetails, authentication.getCredentials(), userDetails.getAuthorities() )) .switchIfEmpty(Mono.error(new RuntimeException("Invalid pre-authenticated user"))); } }
Step 3: Wire Up the Security Filter Chain
Register your converter and manager in a SecurityWebFilterChain, using AuthenticationWebFilter to tie everything together:
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.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.AuthenticationWebFilter; @Configuration @EnableWebFluxSecurity public class WebFluxSecurityConfig { @Bean public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, CustomPreAuthConverter preAuthConverter, ReactiveAuthenticationManager preAuthAuthManager) { // Create the pre-auth filter AuthenticationWebFilter preAuthFilter = new AuthenticationWebFilter(preAuthAuthManager); preAuthFilter.setServerAuthenticationConverter(preAuthConverter); // Add the filter to the chain (place it before other auth filters) return http .authorizeExchange(exchanges -> exchanges .anyExchange().authenticated() ) .addFilterAt(preAuthFilter, ServerHttpSecurity.SecurityWebFiltersOrder.AUTHENTICATION) .build(); } @Bean public CustomPreAuthConverter customPreAuthConverter() { return new CustomPreAuthConverter(); } @Bean public ReactiveAuthenticationManager preAuthAuthManager(ReactiveUserDetailsService userDetailsService) { return new PreAuthAuthenticationManager(userDetailsService); } }
Step 4: Add Success/Failure Handling (Optional)
If you need to handle post-authentication actions (like logging or setting response headers), implement ServerAuthenticationSuccessHandler or ServerAuthenticationFailureHandler and attach them to your AuthenticationWebFilter:
preAuthFilter.setAuthenticationSuccessHandler((exchange, auth) -> { // Custom success logic (e.g., add a response header) exchange.getResponse().getHeaders().add("X-AUTH-SUCCESS", "true"); return Mono.empty(); });
Here's how to map your existing MVC components to WebFlux:
AbstractPreAuthenticatedProcessingFilter→ Replace withAuthenticationWebFilter+ServerAuthenticationConverter(your extraction logic moves to the converter).PreAuthenticatedAuthenticationManager(MVC) → Replace withReactiveAuthenticationManager(ensure all logic usesMono/Fluxfor non-blocking operations).PreAuthenticatedAuthenticationSuccessHandler→ Replace withServerAuthenticationSuccessHandler.- Filter Registration → Instead of registering a servlet filter, configure everything in
SecurityWebFilterChain.
- Never use Servlet Filters in WebFlux: They block threads and break reactive behavior. Stick to
ServerWebFilterimplementations. - Use Reactive Dependencies: If you're looking up user data, use
ReactiveUserDetailsServiceinstead of the traditionalUserDetailsServiceto keep everything non-blocking. - Filter Order Matters: Place your pre-auth filter before other authentication filters (like OAuth2 or Basic Auth) so it takes precedence.
内容的提问来源于stack exchange,提问作者janb

