I'm revisiting this for the purpose of completion and posterity. @Roar's answer hints at the solution but excludes actuals a live project is likelier to entail. Building upon his submission, what currently works involves the following classes:
@Configuration
@EnableWebFluxSecurity
public class SecurityConfig {
@Autowired
private JwtReactiveAuthenticationManager authenticationManager;
@Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(request -> {
CorsConfiguration config = new CorsConfiguration();
// bunch of config
return config;
}))
.authorizeExchange(authorizeExchangeSpec -> {
authorizeExchangeSpec.pathMatchers(
"api/v1/user/login",
"api/v1/user/register","webjars/**",
"/v3/api-docs/**", "/swagger-ui.html",
"/swagger-ui/**").permitAll();
authorizeExchangeSpec.anyExchange().authenticated(); // NOTE THIS
})
.addFilterAt(jwtAuthenticationFilter(), SecurityWebFiltersOrder.AUTHENTICATION)
.build();
}
private AuthenticationWebFilter jwtAuthenticationFilter() {
var authenticationWebFilter = new AuthenticationWebFilter(authenticationManager);
authenticationWebFilter.setServerAuthenticationConverter(exchange -> {
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
var token = authHeader.substring(7);
// System.out.println(token); // correctly logs
// instance is passed to JwtReactiveAuthenticationManager#authenticate
return Mono.just(new UsernamePasswordAuthenticationToken(token, token));
}
return Mono.empty();
});
return authenticationWebFilter;
}
}
@Component
@AllArgsConstructor
public class JwtReactiveAuthenticationManager implements ReactiveAuthenticationManager {
private final JwtTokenProvider jwtTokenProvider;
private final UserRepository userRepository;
@Override
public Mono<Authentication> authenticate(Authentication authentication) {
String token = authentication.getCredentials().toString();
if (!jwtTokenProvider.validateToken(token)) {
System.out.println("Invalid token");
return Mono.empty();
}
String username = jwtTokenProvider.getUsernameFromToken(token);
return userRepository.findByEmail(username)
.switchIfEmpty(Mono.error(new RuntimeException("User not found")))
.map(user -> {
//System.out.println(user); // correctly logs
return new UsernamePasswordAuthenticationToken(user, token,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
});
}
}
And for retrieval on the controller side,
@GetMapping("/{id}")
public ResponseEntity<Flux<SomeDocument>> fetchDocuments(Authentication auth, @PathVariable String id) {
User user = (User) auth.getPrincipal();
// work with user.getId()
}
I believe you can jwtTokenProvider
off some internet tutorial but the above successfully binds decoded user to reactive security context.