Spring WebFlux中的ReactiveSecurityContextHolder为空

11

我正在尝试在Spring WebFlux中使用ReactiveSecurityContextHolder。不幸的是,SecurityContext为空:

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

    }

}

这段代码总是会抛出IllegalStateException异常。

如果我像这里所示这样添加一个subscriberContext:

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")
        );

它可以正常工作并打印出“Hi admin”。但这不是关键点,文章说:“在WebFlux应用程序中,subscriberContext会自动使用ReactorContextWebFilter进行设置。”因此,我应该能够获取已登录的用户。

我的配置如下:

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

}

我这里有什么遗漏吗?如果我在 ReactorContextWebFilter 中设置断点,我可以看到它在每个请求之前都被正确调用。但是我的 ReactiveSecurityContextHolder 总是空的...


你找到解决方案了吗?我最终使用了 org.springframework.web.reactive.function.server.ServerRequest#principal,但我也想使用 ReactiveSecurityContextHolder - neshkeev
不是的,我使用了带有@Controller注释的“传统”符号表示法,并将Principal对象注入到我的方法中。 - Jul13nT
1个回答

14

您必须返回您想要访问ReactiveSecurityContextHolder的流。您不允许在另一个流中进行订阅或者您必须手动执行Reactor上下文切换。

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

我如何在WebFluxTagsContributor.httpRequestTags中访问ReactiveSecurityContextHolder?该方法未返回发布者。 - Vaibhaw K
你能否详细解释一下你在回答标题中提到的理论描述? - Nitin Jha
在传统的基于Servlet的Web应用程序中,SecurityContext保存在ThreadLocal中。如果您不创建新线程,则可以访问Security Context,但是如果您想从另一个线程访问SecurityContext,则必须手动将SecurityContext“移动”/复制到新线程中。对于响应式Web应用程序也是如此,但有所不同。当您启动/订阅新流时,这与在Servlet Web应用程序中创建新线程相同。然后,您必须手动将SecurityContext“移动”/复制到新的Stream / Subscription中。 - xtermi2
请看一下这个链接:https://stackoverflow.com/questions/75062475/reactivesecuritycontextholder-returns-null-in-case-of-blocking-operations - gstackoverflow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接