【问题标题】:ReactiveSecurityContextHolder.getContext() is empty but @AuthenticationPrincipal worksReactiveSecurityContextHolder.getContext() 为空,但 @AuthenticationPrincipal 有效
【发布时间】:2018-12-21 06:13:56
【问题描述】:

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

SecurityContextHolder.getContext().getAuthentication();

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

并且尝试从控制器或组件访问它们会解析为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 -&gt; ctx.getAuthentication()).block() 在控制器中不起作用,但我可以将带有@AuthenticationPrincipal 注释的身份验证主体作为方法参数成功注入控制器中。

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

【问题讨论】:

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


    【解决方案1】:

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

    身份验证按上下文存储。

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

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

    SecurityContextHolder.getContext().getAuthentication();

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

    从方法返回一个反应链,即进行ReactiveSecurityContextHolder.getContext() 调用。

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

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

    【讨论】:

    • 你能举例说明如何以反应方式设置安全上下文吗?
    【解决方案2】:

    我也有类似的问题。所以,我用 JWT 令牌示例上传了我的 github 的 ReactiveSecurityContext。

    https://github.com/heesuk-ahn/spring-webflux-jwt-auth-example

    希望对你有帮助。

    我们需要创建一个自定义authentication filter。 如果该过滤器中的身份验证成功,则它将principal 信息存储在ReactiveSecurityHolder 中。我们可以从控制器访问ReactiveSecurityHolder

    【讨论】:

      【解决方案3】:

      今天通过以下方式解决了这样的问题:

      Authentication authentication = new UserPasswordAuthenticationToken(userCredentials, null, userCredentials.getAuthorities());
      
      ReactiveSecurityContextHolder.getContext()
      .map(context -> context.getAuthentication().getPrincipal())
      .cast(UserCredentials.class)
      .contextWrite(ReactiveSecurityContextHolder.withAuthentication(authentication))
      .subscribe();
      

      UserCredential.class - 是自定义类,包含用户 ID、姓名首字母、登录名和权限。

      【讨论】:

        猜你喜欢
        • 2016-07-12
        • 2015-06-21
        • 1970-01-01
        • 2017-04-24
        • 1970-01-01
        • 1970-01-01
        • 2017-05-29
        • 2013-11-17
        • 2012-12-29
        相关资源
        最近更新 更多