Spring boot .transform/.compose使用Spring Security复制Mono执行

Spring boot .transform/.compose使用Spring Security复制Mono执行,spring-boot,spring-webflux,project-reactor,Spring Boot,Spring Webflux,Project Reactor,在实现基于SpringSecurityResponsive的身份验证解决方案时,我遇到了一个问题,即链中的操作在某个点重复。从那以后,每样东西都叫了两次 罪魁祸首是链条某一点的操作员.transform。编辑被调用的方法并用.flatMap替换操作符后,问题得到解决,所有操作只调用一次 问题 根据"基本法", 函数在装配时应用于原始操作符链,以使用封装的操作符对其进行扩充 及 基本上相当于直接链接操作符 为什么运营商.transform会触发对链的第二次订阅? 上下文 此身份验证流采用受信任的用

在实现基于SpringSecurityResponsive的身份验证解决方案时,我遇到了一个问题,即链中的操作在某个点重复。从那以后,每样东西都叫了两次

罪魁祸首是链条某一点的操作员
.transform
。编辑被调用的方法并用
.flatMap
替换操作符后,问题得到解决,所有操作只调用一次

问题 根据"基本法",

函数在装配时应用于原始操作符链,以使用封装的操作符对其进行扩充

基本上相当于直接链接操作符

为什么运营商
.transform
会触发对链的第二次订阅?

上下文 此身份验证流采用受信任的用户名,并从Web服务检索其详细信息

实现
ReactiveAuthenticationManager
的身份验证方法:

@覆盖
公共Mono身份验证(提供身份验证身份验证){
字符串用户名=(字符串)providedAuthentication.getPrincipal();
字符串标记=(字符串)提供了身份验证。getCredentials();
返回Mono.just(提供身份验证)
.doOnNext(x->LOGGER.debug(“开始验证用户{}”,x))
.doOnNext(AuthenticationValidator.validateProvided)
.then(ReactiveSecurityContextHolder.getContext())
.map(SecurityContext::getAuthentication)
.flatMap(auth->AuthenticationValidator.validateCoherence(auth,providedAuthentication))
.switchIfEmpty(单声道延迟)(()->{
trace(“在检索用户之前如果为空则切换”);
返回retrieveUser(用户名、令牌);
}))
.doOnNext(logAccess);
}
调用的复制从
.switchIfEmpty
的供应商开始,直到链结束


创建由
.switchIfEmpty
使用的
Mono
的方法:

private Mono retrieveUser(字符串用户名、字符串令牌){
返回Mono.just(用户名)
.doOnNext(x->LOGGER.trace(“通过用户名查找之前”))
.then(habilueserdetails.findByUsername(用户名、令牌))
.cast(XXXUserDetails.class)
.transform(角色提供器::提供器)
.map(用户->新建预验证身份验证令牌(用户,GlobalConfiguration.NO\u凭据,用户.getAuthories())
.doOnNext(s->LOGGER.debug(“从XXX检索的用户数据”);
}
第4行的运算符
.transform
已替换为
.flatMap
,以解决此问题


.transform
操作符调用的原始方法:

公共单声道提供商(单声道用户){
返回用户
.map(XXXUserDetails::GetAuthories)
.map(l->StreamHelper.transform(l,GrantedAuthority::getAuthority))
.map(匹配器::匹配)
.map(enricher::enrich)
.map(l->StreamHelper.transform(l,SimpleGrantedAuthority::new))
.zipWith(用户,(权限,用户详细信息)
->CompleteXXXUserDetails.from(userDetails).with allauthorities(authorities));
}

以下是执行的跟踪:

DEBUG 20732---[ctor-http-nio-3]c.a.s.s.h.a.XXXAuthenticationManager:开始用户身份验证[修订]
TRACE 20732---[ctor-http-nio-3]c.a.s.s.h.a.XXXAuthenticationManager:在检索用户之前切换为空
跟踪20732---[ctor-http-nio-3]c.a.s.s.h.a.XXX身份验证管理器:在通过用户名查找之前
TRACE 20732---[ctor-http-nio-3]c.a.s.s.xxx.user.UserRetriever:在请求和调用之间
跟踪20732---[ctor-http-nio-3]c.a.s.s.h.u.retriever.UserRetrieverV01:调用webservice v01
跟踪20732---[ctor-http-nio-3]c.a.s.s.h.a.XXX身份验证管理器:在通过用户名查找之前
TRACE 20732---[ctor-http-nio-3]c.a.s.s.xxx.user.UserRetriever:在请求和调用之间
跟踪20732---[ctor-http-nio-3]c.a.s.s.h.u.retriever.UserRetrieverV01:调用webservice v01

作为参考,我使用的是Spring Boot 2.1.2.RELEASE。

这个答案并没有解决根本原因,而是解释了当订阅多次时如何多次应用
转换,而OP的问题不是这样。将原始文本编辑成引号

只有当
转换
作为订阅链中的顶级运算符应用时,该语句才有效。在这里,您将在
retrieveUser
中应用它,
Mono.defer
中调用(目标是为每个不同的订阅执行该代码)。 (编辑:)因此,如果
延迟
订阅了x次,则转换
功能
也将应用x次

compose
基本上就是
transform
-inside-a-
defer


问题在于你做了一个
user.whatever(…).zipWith(user,…)

使用transform,这将转换为:

Mono<XXXUserDetails> user = Mono.just(username)
    .doOnNext(x -> LOGGER.trace("Before find by username"))
    .then(habileUserDetails.findByUsername(username, token))
    .cast(XXXUserDetails.class);

return user.wathewer(...)
    .zipWith(user, ...);

您可以看到两人如何订阅两次
mono。考虑到这一点,有没有办法在其他级别实现
.transform
的预期行为?我知道,
.defer
的指令在每次订阅时都会执行,但为什么首先会有重复的订阅?由于
.transform
的意外行为?
transform
不会导致订阅,因此它必须来自其他地方。。。如果您查看源代码,
flux.transform(function)
就是
function.apply(flux)
。也许从这个角度,您可以找到为什么应用
provideFor
功能会导致“额外订阅”。顺便说一句,你能告诉我怎么做吗
Mono<XXXUserDetails> user = Mono.just(username)
    .doOnNext(x -> LOGGER.trace("Before find by username"))
    .then(habileUserDetails.findByUsername(username, token))
    .cast(XXXUserDetails.class);

return user.flatMap(u -> {
    Mono<XXXUserDetails> capture = Mono.just(u);
    return capture.whatever(...)
        .zipWith(capture, ...);
}