ReactiveSecurityContextHolder.getContext()为空,但@AuthenticationPrincipal起作用 [英] ReactiveSecurityContextHolder.getContext() is empty but @AuthenticationPrincipal works

查看:509
本文介绍了ReactiveSecurityContextHolder.getContext()为空,但@AuthenticationPrincipal起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在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公共Mono< Authentication>authenticate(最终身份验证身份验证){if(PreAuthentication的身份验证实例){返回Mono.just(认证).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()));}返回Mono.empty();} 

其中 PreAuthentication AbstractAuthenticationToken 的实例,而 AuthenticationToken 扩展了 UsernamePasswordAuthenticationToken

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

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

解决方案

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

身份验证是按上下文存储的.

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

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

SecurityContextHolder.getContext().getAuthentication();

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

从方法返回反应链,该方法正在进行 ReactiveSecurityContextHolder.getContext()调用.

由于您要返回反应式操作符链,因此Spring会对您的链进行预订以执行该操作.当Spring完成时,此处了解更多有关Mono/Flux上下文的信息.是Reactor特有的功能.

I've been using ReactiveAuthenticationManager in Spring Security + Webflux. It is customised to return an instance of UsernamePasswordAuthenticationToken which from what I can tell is an what I should be receiving when I call ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block(). As as far as I can tell I am unable to access the authentication context through both:

SecurityContextHolder.getContext().getAuthentication();

or

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

And attempting to access those from controllers or components resolves to null. I had some doubts about whether I am really returning an Authentication instance in my custom manager and it seems like I am:

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

Where PreAuthentication is an instance of a AbstractAuthenticationToken and AuthenticationToken extends UsernamePasswordAuthenticationToken

Interestingly although ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block() does not work in a controller I can inject the authentication principal with the @AuthenticationPrincipal annotation as a method parameter successfully in a controller.

This seems like a configuration issue but I cannot tell where. Anyone have any idea why I cannot return authentication?

解决方案

Since you are using WebFlux, you are handling requests using event-loop. You are not using thread-per-request model anymore, as with Tomcat.

Authentication is stored per context.

With Tomcat, when request arrives, Spring stores authentication in SecurityContextHolder. SecurityContextHolder uses ThreadLocal variable to store authentication. Specific authentication object is visible to you, only if you are trying to fetch it from ThreadLocal object, in the same thread, in which it was set. Thats why you could get authentication in controller via static call. ThreadLocal object knows what to return to you, because it knows your context - your thread.

With WebFlux, you could handle all requests using just 1 thread. Static call like this won't return expected results anymore:

SecurityContextHolder.getContext().getAuthentication();

Because there is no way to use ThreadLocal objects anymore. The only way to get Authentication for you, is to ask for it in controller's method signature, or...

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

Since you are returning a chain of reactive operators, Spring make a subscription to your chain, in order to execute it. When Spring does it, it provides a security context to whole chain. Thus every call ReactiveSecurityContextHolder.getContext() inside this chain, will return expected data.

Your can read more about Mono/Flux context here, because it is a Reactor-specific feature.

这篇关于ReactiveSecurityContextHolder.getContext()为空,但@AuthenticationPrincipal起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆