ReactiveSecurityContextHolder.getContext() 为空但 @AuthenticationPrincipal 有效 [英] ReactiveSecurityContextHolder.getContext() is empty but @AuthenticationPrincipal works

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

问题描述

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

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

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

并且尝试从控制器或组件访问它们会解析为 null.我对我是否真的在我的自定义管理器中返回了一个 Authentication 实例有一些疑问,看起来我是:

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

其中 PreAuthenticationAbstractAuthenticationToken 的实例,AuthenticationToken 扩展 UsernamePasswordAuthenticationToken

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

有趣的是,虽然 ReactiveSecurityContextHolder.getContext().map(ctx -> ctx.getAuthentication()).block() 在控制器中不起作用,但我可以使用 @AuthenticationPrincipal 注释作为方法参数在控制器中成功.

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?

推荐答案

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

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.

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

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.

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

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

SecurityContextHolder.getContext().getAuthentication();

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

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...

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

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

由于您要返回一系列反应式操作符,Spring 会订阅您的链,以便执行它.当 Spring 这样做时,它为整个链提供安全上下文.因此,此链中的每个调用 ReactiveSecurityContextHolder.getContext() 都会返回预期的数据.

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.

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

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

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

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