问题描述
我一直在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对象的方法.为您获取身份验证的唯一方法是在控制器的方法签名中要求身份验证,或者...
由于您要返回反应式操作符链,因此Spring会对您的链进行预订以执行该操作.当Spring完成时,它为整个链提供了安全上下文.因此,此链中的每个调用 ReactiveSecurityContextHolder.getContext()
都将返回预期的数据.
您可以在此处了解更多有关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.
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...
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起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!