java - ReactiveSecurityContextHolder.getContext() 为空但@AuthenticationPrincipal 有效

标签 java spring spring-security spring-webflux project-reactor

我一直在 Spring Security + Webflux 中使用 ReactiveAuthenticationManager。它被定制为返回 UsernamePasswordAuthenticationToken 的实例,据我所知,这是我调用 ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication( )). block ()。据我所知,我无法通过以下两种方式访问​​身份验证上下文:

SecurityContextHolder.getContext().getAuthentication();

ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block()

并且尝试从 Controller 或组件访问这些解析为 null。我怀疑我是否真的在我的自定义管理器中返回了一个 Authentication 实例,看起来我是:

@Override
public Mono<Authentication> authenticate(final Authentication authentication) {
    if (authentication instanceof PreAuthentication) {
        return Mono.just(authentication)
            .publishOn(Schedulers.parallel())
            .switchIfEmpty(Mono.defer(this::throwCredentialError))
            .cast(PreAuthentication.class)
            .flatMap(this::authenticatePayload)
            .publishOn(Schedulers.parallel())
            .onErrorResume(e -> throwCredentialError())
            .map(userDetails -> new AuthenticationToken(userDetails, userDetails.getAuthorities()));
    }

    return Mono.empty();
}

PreAuthenticationAbstractAuthenticationToken 的实例,AuthenticationToken 扩展 UsernamePasswordAuthenticationToken

有趣的是,尽管 ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block() 在 Controller 中不起作用,但我可以使用 注入(inject)身份验证主体@AuthenticationPrincipal 注解作为 Controller 中的方法参数成功。

这似乎是一个配置问题,但我不知道是哪里。有人知道为什么我无法返回身份验证吗?

最佳答案

由于您使用的是 WebFlux,因此您正在使用事件循环处理请求。 您不再像 Tomcat 那样使用每个请求线程模型。

Authentication is stored per context.

使用 Tomcat,当请求到达时,Spring 将身份验证 存储在SecurityContextHolder 中。 SecurityContextHolder 使用 ThreadLocal 变量来存储authentication。特定的 authentication 对象对你可见,只有当你试图从 ThreadLocal 对象中获取它时,在同一个线程中,它被设置在其中。 这就是为什么您可以通过静态调用在 Controller 中获得身份验证。 ThreadLocal 对象知道返回给您什么,因为它知道您的上下文 - 您的线程。

使用 WebFlux,您可以仅使用 1 个线程处理所有请求。 像这样的静态调用将不再返回预期结果:

SecurityContextHolder.getContext().getAuthentication();

因为没有办法再使用ThreadLocal对象了。 为您获得身份验证的唯一方法是在 Controller 的方法签名中请求它,或者...

Return a reactive-chain from method, that is making a ReactiveSecurityContextHolder.getContext() call.

由于您要返回响应式(Reactive)运算符链,Spring 会订阅您的链,以便执行它。 当 Spring 执行时,it provides a security context to whole chain .因此,此链中的每次调用 ReactiveSecurityContextHolder.getContext() 都将返回预期的数据。

您可以阅读有关 Mono/Flux 上下文的更多信息 here ,因为它是 Reactor 特有的功能。

关于java - ReactiveSecurityContextHolder.getContext() 为空但@AuthenticationPrincipal 有效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51315378/

相关文章:

java - Java 中的 LDAP 事务

java - 注册为 Spring bean 时过滤器调用两次

java - Array List .remove 函数不会完全删除元素,在 JTable 中留下空白空间

java - ORA-01000 : maximum open cursors exceededwhen using Spring SimpleJDBCCall

java - apache commons 的两个版本 - 使用特定的一个

java - 如何在 testng 工厂类中使用 spring Autowiring

java - 如何识别maven项目中潜在的java依赖命名空间冲突?

spring-security - Spring Security OAuth2 SSO 与自定义提供程序 + 注销

java - 是否有必要保护 JAX-RS 请求免受 CSRF 攻击?

java - 使 Node JS 功能同步?在查询数据库之前发送json响应