.transform / .compose дублирует выполнение Mono в Spring Security - PullRequest
0 голосов
/ 05 февраля 2019

При реализации решения для аутентификации на основе Spring Security Reactive я столкнулся с проблемой, когда в какой-то момент операции в цепочке дублируются.От этого все было вызвано дважды.

Виновником был оператор .transform в одной точке цепи.После редактирования вызванного метода и замены оператора на .flatMap проблема была решена, и все вызывалось только один раз.

Вопрос

Согласно документации оператора функция

применяется к исходной цепочке операторов во время сборки, чтобы дополнить ее инкапсулированными операторами

и

в основномэквивалентно непосредственному связыванию операторов.

Почему оператор .transform инициировал вторую подписку на цепочку, тогда?

Контекст

Этот поток аутентификации принимает доверенное имя пользователя и получает его данные из веб-службы.

Метод аутентификации для реализации ReactiveAuthenticationManager:

@Override
public Mono<Authentication> authenticate(Authentication providedAuthentication) {
    String username = (String) providedAuthentication.getPrincipal();
    String token = (String) providedAuthentication.getCredentials();

    return Mono.just(providedAuthentication)
            .doOnNext(x -> LOGGER.debug("Starting authentication of user {}", x))
            .doOnNext(AuthenticationValidator.validateProvided)
            .then(ReactiveSecurityContextHolder.getContext())
            .map(SecurityContext::getAuthentication)
            .flatMap(auth -> AuthenticationValidator.validateCoherence(auth, providedAuthentication))
            .switchIfEmpty(Mono.defer(() -> {
                LOGGER.trace("Switch if empty before retrieving user");
                return retrieveUser(username, token);
            }))
            .doOnNext(logAccess);
}

Дублирование вызовов началось споставщик .switchIfEmpty до конца цепочки.


Метод создания Mono, используемый .switchIfEmpty:

private Mono<PreAuthenticatedAuthenticationToken> retrieveUser(String username, String token) {
    return  Mono.just(username)
            .doOnNext(x -> LOGGER.trace("Before find by username"))
            .then(habileUserDetails.findByUsername(username, token))
            .cast(XXXUserDetails.class)
            .transform(rolesProvider::provideFor)
            .map(user -> new PreAuthenticatedAuthenticationToken(user, GlobalConfiguration.NO_CREDENTIAL, user.getAuthorities()))
            .doOnNext(s -> LOGGER.debug("User data retrieved from XXX"));
}

Оператор .transform onстрока 4 имеетбыл заменен на .flatMap для решения проблемы.


Исходный метод, вызываемый оператором .transform:

public Mono<CompleteXXXUserDetails> provideFor(Mono<XXXUserDetails> user) {
    return user
        .map(XXXUserDetails::getAuthorities)
        .map(l -> StreamHelper.transform(l, GrantedAuthority::getAuthority))
        .map(matcher::match)
        .map(enricher::enrich)
        .map(l -> StreamHelper.transform(l, SimpleGrantedAuthority::new))
        .zipWith(user, (authorities, userDetails)
            -> CompleteXXXUserDetails.from(userDetails).withAllAuthorities(authorities));
}

Вот след выполнения:

DEBUG 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager  : Starting authentication of user [REDACTED]
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager  : Switch if empty before retrieving user
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager  : Before find by username
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.xxx.user.UserRetriever        : Between request and call
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.u.retriever.UserRetrieverV01: Calling webservice v01
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.a.XXXAuthenticationManager  : Before find by username
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.xxx.user.UserRetriever        : Between request and call
TRACE 20732 --- [ctor-http-nio-3] c.a.s.s.h.u.retriever.UserRetrieverV01: Calling webservice v01

Для информации я использую Spring Boot 2.1.2.RELEASE.

Ответы [ 2 ]

0 голосов
/ 06 февраля 2019

Проблема в том, что вы делаете user.whatever(...).zipWith(user, ...).

С преобразованием, это переводит в:

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, ...);

В то время как с flatMap я предполагаю, что вы сделали что-то для эффектаflatMap(u -> provideFor(Mono.just(u))?Если это так, это будет означать:

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, ...);
}

Вы можете видеть, как оба подписываются дважды на a Mono<XXXUserDetails из-за zipWith.

Причина в том, что кажется, , чтобы подписаться один раз с flatMap, потому что он захватывает выходные данные восходящего конвейера и применяет функцию provideFor для этого захвата.Захват (Mono.just(u)) подписывается дважды, но действует как кеш и не несет никакой логики / журналов / и т.д. ...

С transform захват не происходит.Функция provideFor применяется непосредственно к восходящему конвейеру, что делает факт подписки дважды вполне видимым.

0 голосов
/ 06 февраля 2019

Этот ответ не затрагивает основную причину , а скорее объясняет, как transform может применяться несколько раз при подписке несколько раз, что не относится к проблеме ОП.Отредактировал исходный текст в цитате.

Этот оператор действителен только в том случае, если transform применяется в качестве оператора верхнего уровня в цепочке, на которую вы подписаны.Здесь вы применяете его в retrieveUser, , который вызывается внутри Mono.defer (цель которого - выполнить этот код для каждой отдельной подписки).(edit :), так что если defer подписан на x раз, преобразование Function будет применено также x раз.

compose в основном transform -inside-a- defer кстати.

...