11

I am trying to use the ReactiveSecurityContextHolder with Spring WebFlux. Unfortunately, the SecurityContext is empty :

@Configuration
public class Router {

    @Bean
    public RouterFunction<ServerResponse> routes(Handler handler) {
        return nest(
                path("/bill"),
                route(
                        GET("/").and(accept(APPLICATION_JSON)), handler::all));
    }

    @Component
    class Handler {

        Mono<ServerResponse> all(ServerRequest request) {

            ReactiveSecurityContextHolder.getContext()
                .switchIfEmpty(Mono.error(new IllegalStateException("ReactiveSecurityContext is empty")))
                .map(SecurityContext::getAuthentication)
                .map(Authentication::getName)
                .flatMap(s -> Mono.just("Hi " + s))
                .subscribe(
                        System.out::println,
                        Throwable::printStackTrace,
                        () -> System.out.println("completed without a value")
                );

            return ok().build();
        }

    }

}

This code always throws the IllegalStateException.

If I add a subscriberContext like shown here :

Authentication authentication = new TestingAuthenticationToken("admin", "password", "ROLE_ADMIN");

ReactiveSecurityContextHolder.getContext()
        .switchIfEmpty(Mono.error(new IllegalStateException("ReactiveSecurityContext is empty")))
        .map(SecurityContext::getAuthentication)
        .map(Authentication::getName)
        .flatMap(s -> Mono.just("Hi " + s))
        .subscriberContext(ReactiveSecurityContextHolder.withAuthentication(authentication))
        .subscribe(
                System.out::println,
                Throwable::printStackTrace,
                () -> System.out.println("completed without a value")
        );

It works fine and print "Hi admin". But that's not the point, the article says "In a WebFlux application the subscriberContext is automatically setup using ReactorContextWebFilter". So I should be able to fetch the logged user.

I have this configuration :

@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class SecurityConfig {

    @Bean
    public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
        return http.authorizeExchange()
            .anyExchange().authenticated()
            .and().formLogin()
            .and().build();
    }

    @Bean
    public MapReactiveUserDetailsService userDetailsService() {
        UserDetails user = User.withDefaultPasswordEncoder()
            .username("user")
            .password("password")
            .roles("USER")
            .build();

        UserDetails admin = User.withDefaultPasswordEncoder()
            .username("admin")
            .password("password")
            .roles("ADMIN")
            .build();

        return new MapReactiveUserDetailsService(user, admin);
    }

}

Am I missing something here ? If I put breakpoints in ReactorContextWebFilter, I can see that it is correctly called before each request. But my ReactiveSecurityContextHolder is always empty...

Jul13nT
  • 657
  • 2
  • 9
  • 19
  • Have you managed to find a solution? I ended up using `org.springframework.web.reactive.function.server.ServerRequest#principal`, but I would like to use `ReactiveSecurityContextHolder` too – neshkeev Jun 30 '18 at 20:47
  • Nope, I used the "classic" notation with @Controller annotations and I injected the Principal object in my methods. – Jul13nT Jul 02 '18 at 07:02

1 Answers1

14

You have to return the stream in which you want to access ReactiveSecurityContextHolder. You're not allowed to subscribe within another stream OR you have to do the Reactor context switch manually.

@Component
class Handler {
    Mono<ServerResponse> all(ServerRequest request) {
        return ReactiveSecurityContextHolder.getContext()
                .switchIfEmpty(Mono.error(new IllegalStateException("ReactiveSecurityContext is empty")))
                .map(SecurityContext::getAuthentication)
                .map(Authentication::getName)
                .flatMap(s -> Mono.just("Hi " + s))
                .doOnNext(System.out::println)
                .doOnError(Throwable::printStackTrace)
                .doOnSuccess(s -> System.out.println("completed without value: " + s))
                .flatMap(s -> ServerResponse.ok().build());
    }
}
xtermi2
  • 470
  • 5
  • 10
  • How do I access ReactiveSecurityContextHolder within WebFluxTagsContributor.httpRequestTags? The method is not returning a publisher – Vaibhaw K Mar 12 '21 at 05:34
  • Can you please explain more about the theoretical description which you have mentioned in the title of your answer. – Nitin Jha May 29 '21 at 15:00
  • OK, in a traditional Servlet based WebApp the SecurityContext is hold in a ThreadLoacal. If you don't create new Threads you can access the Security Context, but if you want to access the SecurityContext from another Thread, you have to manually "move" / copy the SecutiryContext from your current Thread to the new one. The same applies for Reactive WebApps, but a bit different. When you start/subscribe a new Stream, it's the same as creating a new Thread in a Servlet WebApp. Then you have to manually "move" / copy the SecurityContext to the new Stream/Subscription. – xtermi2 Aug 02 '21 at 06:10
  • Could you please take a look: https://stackoverflow.com/questions/75062475/reactivesecuritycontextholder-returns-null-in-case-of-blocking-operations – gstackoverflow Jan 09 '23 at 20:15