Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test using @WithMockUser fails with 401 UNAUTHORIZED with 3.2 #14207

Closed
andrashatvani opened this issue Nov 28, 2023 · 17 comments
Closed

Test using @WithMockUser fails with 401 UNAUTHORIZED with 3.2 #14207

andrashatvani opened this issue Nov 28, 2023 · 17 comments
Assignees
Labels
in: test An issue in spring-security-test type: bug A general bug
Milestone

Comments

@andrashatvani
Copy link

Describe the bug
The following test works with 3.1, but fails with 401 UNAUTHORIZED with 3.2:

To Reproduce

@SpringBootTest(
    webEnvironment = RANDOM_PORT,
    classes = [Application::class],
    properties = ["spring.main.allow-bean-definition-overriding=true"]
)
@TestConstructor(autowireMode = ALL)
@WithMockUser(authorities = [ROLE_USER])
class HandlerIntegrationTest(
    @MockkBean
    private val searcher: Searcher,
    private val client: WebTestClient,
) {
    @Test
    fun search() {
        val searchResponse = SearchResponse(
            result = listOf(),
            page = 1,
            pageSize = 20,
            total = 0,
            filterOptions = FilterOptions(setOf(), setOf()),
            sort = setOf(),
            descending = listOf(false),
        )
        coEvery { searcher.search(any()) } returns searchResponse
        client.get()
            .uri { it.path("$ENDPOINT/search").build() }
            .exchange()
            .expectStatus()
            .isOk
            .expectBody<SearchResponse>()
            .consumeWith {
                assertThat(it.responseBody).isEqualTo(searchResponse)
            }
    }
}

A custom MapReactiveUserDetailsService has been in place and it looks like this:

    @Bean
    @ConditionalOnProperty(value = ["spring.security.user.passwordGenerated"], matchIfMissing = true, havingValue = "false")
    fun userDetailsService(): MapReactiveUserDetailsService {
        val actuatorUser = User
            .withUsername(securityProperties.user.name)
            .password("{noop}${securityProperties.user.password}")
            .authorities(AUTHORITY_ACTUATOR, AUTHORITY_ACCESS_MONITORING, AUTHORITY_ACCESS_INTERNAL_API).build()

        val monitoringUser = User
            .withUsername(monitoringProperties.username)
            .password("{noop}${monitoringProperties.password}")
            .authorities(AUTHORITY_ACCESS_MONITORING)
            .build()

        return MapReactiveUserDetailsService(actuatorUser, monitoringUser)
    }

Expected behavior
The test works

Sample
Currently we have tried in 3 different projects with different services always with this same result, thus this might not be an isolated case, but rather a major issue, thus hopefully you can easily identify the cause.

@RomanKosovnenko
Copy link

I have the same issue after upgrade to 3.2.0 from Spring-Boot 3.1.5

@ghost
Copy link

ghost commented Nov 29, 2023

I have the same issue after upgrade to 3.2.0 from Spring-Boot 3.1.5

Same for me.

@marcusdacoregio marcusdacoregio self-assigned this Nov 29, 2023
@marcusdacoregio marcusdacoregio removed the status: waiting-for-triage An issue we've not yet triaged label Nov 29, 2023
@marcusdacoregio
Copy link
Contributor

Hi @andrashatvani, thanks for the report.

Is it possible to provide a minimal, reproducible sample? Or if @RomanKosovnenko and @frederikmartin1337 could provide one, it would be of great value.

@credmond-git
Copy link

credmond-git commented Dec 4, 2023

We are facing a similar issue with webflux and spring security.
It seems like in Spring Boot 3.2 we get this message

2023-12-04 10:16:02.207 DEBUG [     parallel-1] a.DelegatingReactiveAuthorizationManager : Checking authorization on '/secureGreeting/admin' using org.springframework.security.config.web.server.ServerHttpSecurity$AuthorizeExchangeSpec$Access$$Lambda$1567/0x000001783ba35570@11c3f649 []
2023-12-04 10:16:02.211 DEBUG [     parallel-1] o.s.s.w.s.a.AuthorizationWebFilter       : Authorization successful []
2023-12-04 10:16:02.449 DEBUG [ctor-http-nio-4] ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@6ee73ec4' []
2023-12-04 10:16:02.500 DEBUG [ctor-http-nio-4] ebSessionServerSecurityContextRepository : No SecurityContext found in WebSession: 'org.springframework.web.server.session.InMemoryWebSessionStore$InMemoryWebSession@6ee73ec4' []
2023-12-04 10:16:02.500 DEBUG [ctor-http-nio-4] DelegatingServerAuthenticationEntryPoint : Trying to match using org.springframework.security.config.web.server.ServerHttpSecurity$HttpBasicSpec$$Lambda$1570/0x000001783ba36000@5207bcd6 []
2023-12-04 10:16:02.503 DEBUG [ctor-http-nio-4] DelegatingServerAuthenticationEntryPoint : No match found. Using default entry point org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint@7355b4d8 []

that we did not get in Spring Boot 3.1

2023-12-04 10:13:48.357 DEBUG [     parallel-1] a.DelegatingReactiveAuthorizationManager : Checking authorization on '/secureGreeting/admin' using org.springframework.security.config.web.server.ServerHttpSecurity$AuthorizeExchangeSpec$Access$$Lambda$1543/0x000002da2ba06690@7924cf9 []
2023-12-04 10:13:48.359 DEBUG [     parallel-1] o.s.s.w.s.a.AuthorizationWebFilter       : Authorization successful []
2023-12-04 10:13:48.522  INFO [ctor-http-nio-4] c.e.c.s.c.RoleGreetingController         : adminUser 47rtixo2qy24p40i6ggv3cve9 []

I tried to create a minimal report but was not able to reproduce it.

It seems like somewhere along the way it loses its security context.
I dont know if it is related but when debugging i can see in most places the below callstack that populates the security context.

<init>:75, Context4 (reactor.util.context)
put:69, Context3 (reactor.util.context)
currentContext:104, ReactorContextTestExecutionListener$DelegateTestExecutionListener$SecuritySubContext (org.springframework.security.test.context.support)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
currentContext:33, InnerOperator (reactor.core.publisher)
<init>:78, FluxFilterFuseable$FilterFuseableSubscriber (reactor.core.publisher)
subscribeOrReturn:47, MonoFilterFuseable (reactor.core.publisher)
subscribe:63, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribeNext:264, MonoIgnoreThen$ThenIgnoreMain (reactor.core.publisher)
subscribe:51, MonoIgnoreThen (reactor.core.publisher)
subscribe:4512, Mono (reactor.core.publisher)
onComplete:82, FluxSwitchIfEmpty$SwitchIfEmptySubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onNext:155, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.filter] filter:112, AuthenticationWebFilter (org.springframework.security.web.server.authentication)
onNext:113, FluxFilter$FilterSubscriber (reactor.core.publisher)
[Mono.just] match:83, ServerWebExchangeMatcher$MatchResult (org.springframework.security.web.server.util.matcher)
request:2571, Operators$ScalarSubscription (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:186, FluxFilter$FilterSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:194, MonoFlatMap$FlatMapMain (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
set:2367, Operators$MultiSubscriptionSubscriber (reactor.core.publisher)
onSubscribe:2241, Operators$MultiSubscriptionSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:117, MonoFlatMap$FlatMapMain (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:85, FluxFilter$FilterSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
subscribe:55, MonoJust (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:55, MonoDeferContextual (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
onNext:165, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.map] filter:62, WebFilterChainProxy (org.springframework.security.web.server)
onNext:129, FluxMapFuseable$MapFuseableSubscriber (reactor.core.publisher)
[Mono.flatMap] filter:61, WebFilterChainProxy (org.springframework.security.web.server)
secondComplete:245, MonoFlatMap$FlatMapMain (reactor.core.publisher)
onNext:305, MonoFlatMap$FlatMapInner (reactor.core.publisher)
[Flux.collectList] lambda$filter$2:61, WebFilterChainProxy (org.springframework.security.web.server)
completePossiblyEmpty:2097, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
onComplete:118, MonoCollectList$MonoCollectListSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
fastPath:424, FluxIterable$IterableSubscription (reactor.core.publisher)
request:291, FluxIterable$IterableSubscription (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:2067, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:291, MonoFlatMap$FlatMapInner (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:2051, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
subscribe:201, FluxIterable (reactor.core.publisher)
subscribe:83, FluxIterable (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
onNext:165, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.switchIfEmpty] filter:59, WebFilterChainProxy (org.springframework.security.web.server)
onNext:74, FluxSwitchIfEmpty$SwitchIfEmptySubscriber (reactor.core.publisher)
[Flux.next] filter:58, WebFilterChainProxy (org.springframework.security.web.server)
onNext:82, MonoNext$NextSubscriber (reactor.core.publisher)
[Flux.filterWhen] filter:57, WebFilterChainProxy (org.springframework.security.web.server)
drain:301, FluxFilterWhen$FluxFilterWhenSubscriber (reactor.core.publisher)
onNext:140, FluxFilterWhen$FluxFilterWhenSubscriber (reactor.core.publisher)
[Flux.fromIterable] filter:56, WebFilterChainProxy (org.springframework.security.web.server)
slowPath:335, FluxIterable$IterableSubscription (reactor.core.publisher)
request:294, FluxIterable$IterableSubscription (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:200, FluxFilterWhen$FluxFilterWhenSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
subscribe:201, FluxIterable (reactor.core.publisher)
subscribe:83, FluxIterable (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribeNext:264, MonoIgnoreThen$ThenIgnoreMain (reactor.core.publisher)
subscribe:51, MonoIgnoreThen (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribeNext:264, MonoIgnoreThen$ThenIgnoreMain (reactor.core.publisher)
subscribe:51, MonoIgnoreThen (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:55, MonoDeferContextual (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
onStateChange:1169, HttpServer$HttpServerHandle (reactor.netty.http.server)
onStateChange:710, ReactorNetty$CompositeConnectionObserver (reactor.netty)
onStateChange:481, ServerTransport$ChildObserver (reactor.netty.transport)
onInboundNext:652, HttpServerOperations (reactor.netty.http.server)
channelRead:114, ChannelOperationsHandler (reactor.netty.channel)
invokeChannelRead:444, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
channelRead:238, HttpTrafficHandler (reactor.netty.http.server)
invokeChannelRead:442, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:436, CombinedChannelDuplexHandler$DelegatingChannelHandlerContext (io.netty.channel)
fireChannelRead:346, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:318, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:251, CombinedChannelDuplexHandler (io.netty.channel)
invokeChannelRead:442, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1410, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:440, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:919, DefaultChannelPipeline (io.netty.channel)
read:166, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:788, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:724, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:650, NioEventLoop (io.netty.channel.nio)
run:562, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:840, Thread (java.lang)

But here it is not populating the security context and the context mono is empty. After this it fails.

<init>:75, Context4 (reactor.util.context)
put:90, Context4 (reactor.util.context)
lambda$wrap$5:565, ObservationWebFilterChainDecorator$WebFilterObservation$SimpleWebFilterObservation (org.springframework.security.web.server)
apply:-1, ObservationWebFilterChainDecorator$WebFilterObservation$SimpleWebFilterObservation$$Lambda$3062/0x0000015f49ecd670 (org.springframework.security.web.server)
subscribeOrReturn:38, MonoContextWrite (reactor.core.publisher)
subscribe:63, InternalMonoOperator (reactor.core.publisher)
subscribe:55, MonoDeferContextual (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:4512, Mono (reactor.core.publisher)
onComplete:82, FluxSwitchIfEmpty$SwitchIfEmptySubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onComplete:299, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onComplete:299, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onNext:155, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.switchIfEmpty] verify:53, ReactiveAuthorizationManager (org.springframework.security.authorization)
onNext:74, FluxSwitchIfEmpty$SwitchIfEmptySubscriber (reactor.core.publisher)
[Mono.filter] verify:52, ReactiveAuthorizationManager (org.springframework.security.authorization)
onNext:118, FluxFilterFuseable$FilterFuseableSubscriber (reactor.core.publisher)
[Mono.deferContextual] check:56, ObservationReactiveAuthorizationManager (org.springframework.security.authorization)
[Mono.doOnError] lambda$check$4:66, ObservationReactiveAuthorizationManager (org.springframework.security.authorization)
onNext:180, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
[Mono.doOnCancel] lambda$check$4:66, ObservationReactiveAuthorizationManager (org.springframework.security.authorization)
onNext:503, FluxPeekFuseable$PeekFuseableConditionalSubscriber (reactor.core.publisher)
[Mono.doOnSuccess] lambda$check$4:60, ObservationReactiveAuthorizationManager (org.springframework.security.authorization)
onNext:180, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
[Mono.defaultIfEmpty] check:65, DelegatingReactiveAuthorizationManager (org.springframework.security.web.server.authorization)
onNext:122, FluxDefaultIfEmpty$DefaultIfEmptySubscriber (reactor.core.publisher)
[Flux.next] check:64, DelegatingReactiveAuthorizationManager (org.springframework.security.web.server.authorization)
onNext:82, MonoNext$NextSubscriber (reactor.core.publisher)
[Flux.concatMap] check:54, DelegatingReactiveAuthorizationManager (org.springframework.security.web.server.authorization)
innerNext:258, FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber (reactor.core.publisher)
onNext:863, FluxConcatMap$ConcatMapInner (reactor.core.publisher)
[Mono.flatMap] lambda$check$2:58, DelegatingReactiveAuthorizationManager (org.springframework.security.web.server.authorization)
onNext:158, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.map] lambda$check$2:57, DelegatingReactiveAuthorizationManager (org.springframework.security.web.server.authorization)
onNext:129, FluxMapFuseable$MapFuseableSubscriber (reactor.core.publisher)
[Mono.filter] lambda$check$2:56, DelegatingReactiveAuthorizationManager (org.springframework.security.web.server.authorization)
onNext:113, FluxFilter$FilterSubscriber (reactor.core.publisher)
[Mono.just] match:83, ServerWebExchangeMatcher$MatchResult (org.springframework.security.web.server.util.matcher)
request:2571, Operators$ScalarSubscription (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:186, FluxFilter$FilterSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:171, FluxMapFuseable$MapFuseableSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:194, MonoFlatMap$FlatMapMain (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:2331, Operators$MultiSubscriptionSubscriber (reactor.core.publisher)
request:338, FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:108, MonoNext$NextSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:98, FluxDefaultIfEmpty$DefaultIfEmptySubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:139, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:437, FluxPeekFuseable$PeekFuseableConditionalSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:139, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:191, FluxFilterFuseable$FilterFuseableSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
set:2367, Operators$MultiSubscriptionSubscriber (reactor.core.publisher)
onSubscribe:2241, Operators$MultiSubscriptionSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:87, FluxFilterFuseable$FilterFuseableSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:152, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:471, FluxPeekFuseable$PeekFuseableConditionalSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:152, MonoPeekTerminal$MonoTerminalPeekSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:2051, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:70, MonoNext$NextSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:164, FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
subscribe:201, FluxIterable (reactor.core.publisher)
subscribe:83, FluxIterable (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:55, MonoDeferContextual (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
onNext:165, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.defaultIfEmpty] filter:40, ServerRequestCacheWebFilter (org.springframework.security.web.server.savedrequest)
completePossiblyEmpty:2097, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
onComplete:134, FluxDefaultIfEmpty$DefaultIfEmptySubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onComplete:152, FluxMapFuseable$MapFuseableSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onComplete:152, FluxMapFuseable$MapFuseableSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onComplete:171, FluxFilterFuseable$FilterFuseableSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onComplete:350, FluxMapFuseable$MapFuseableConditionalSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
complete:1866, Operators$MonoSubscriber (reactor.core.publisher)
subscribeOrReturn:151, MonoCacheTime (reactor.core.publisher)
subscribe:63, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribeNext:264, MonoIgnoreThen$ThenIgnoreMain (reactor.core.publisher)
subscribe:51, MonoIgnoreThen (reactor.core.publisher)
subscribe:4512, Mono (reactor.core.publisher)
onComplete:82, FluxSwitchIfEmpty$SwitchIfEmptySubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onNext:155, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.filter] filter:112, AuthenticationWebFilter (org.springframework.security.web.server.authentication)
onNext:113, FluxFilter$FilterSubscriber (reactor.core.publisher)
[Mono.just] match:83, ServerWebExchangeMatcher$MatchResult (org.springframework.security.web.server.util.matcher)
request:2571, Operators$ScalarSubscription (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:186, FluxFilter$FilterSubscriber (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:194, MonoFlatMap$FlatMapMain (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
set:2367, Operators$MultiSubscriptionSubscriber (reactor.core.publisher)
onSubscribe:2241, Operators$MultiSubscriptionSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:117, MonoFlatMap$FlatMapMain (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:85, FluxFilter$FilterSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
subscribe:55, MonoJust (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:55, MonoDeferContextual (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
onNext:165, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.map] filter:62, WebFilterChainProxy (org.springframework.security.web.server)
onNext:129, FluxMapFuseable$MapFuseableSubscriber (reactor.core.publisher)
[Mono.flatMap] filter:61, WebFilterChainProxy (org.springframework.security.web.server)
secondComplete:245, MonoFlatMap$FlatMapMain (reactor.core.publisher)
onNext:305, MonoFlatMap$FlatMapInner (reactor.core.publisher)
[Flux.collectList] lambda$filter$2:61, WebFilterChainProxy (org.springframework.security.web.server)
completePossiblyEmpty:2097, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
onComplete:118, MonoCollectList$MonoCollectListSubscriber (reactor.core.publisher)
onComplete:549, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
fastPath:424, FluxIterable$IterableSubscription (reactor.core.publisher)
request:291, FluxIterable$IterableSubscription (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
request:2067, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:291, MonoFlatMap$FlatMapInner (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:2051, Operators$BaseFluxToMonoOperator (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
subscribe:201, FluxIterable (reactor.core.publisher)
subscribe:83, FluxIterable (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
onNext:165, MonoFlatMap$FlatMapMain (reactor.core.publisher)
[Mono.switchIfEmpty] filter:59, WebFilterChainProxy (org.springframework.security.web.server)
onNext:74, FluxSwitchIfEmpty$SwitchIfEmptySubscriber (reactor.core.publisher)
[Flux.next] filter:58, WebFilterChainProxy (org.springframework.security.web.server)
onNext:82, MonoNext$NextSubscriber (reactor.core.publisher)
[Flux.filterWhen] filter:57, WebFilterChainProxy (org.springframework.security.web.server)
drain:301, FluxFilterWhen$FluxFilterWhenSubscriber (reactor.core.publisher)
onNext:140, FluxFilterWhen$FluxFilterWhenSubscriber (reactor.core.publisher)
[Flux.fromIterable] filter:56, WebFilterChainProxy (org.springframework.security.web.server)
slowPath:335, FluxIterable$IterableSubscription (reactor.core.publisher)
request:294, FluxIterable$IterableSubscription (reactor.core.publisher)
request:649, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
onSubscribe:200, FluxFilterWhen$FluxFilterWhenSubscriber (reactor.core.publisher)
onSubscribe:633, FluxOnAssembly$OnAssemblySubscriber (reactor.core.publisher)
subscribe:201, FluxIterable (reactor.core.publisher)
subscribe:83, FluxIterable (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:53, MonoDefer (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribeNext:264, MonoIgnoreThen$ThenIgnoreMain (reactor.core.publisher)
subscribe:51, MonoIgnoreThen (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribeNext:264, MonoIgnoreThen$ThenIgnoreMain (reactor.core.publisher)
subscribe:51, MonoIgnoreThen (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
subscribe:55, MonoDeferContextual (reactor.core.publisher)
subscribe:76, InternalMonoOperator (reactor.core.publisher)
onStateChange:1169, HttpServer$HttpServerHandle (reactor.netty.http.server)
onStateChange:710, ReactorNetty$CompositeConnectionObserver (reactor.netty)
onStateChange:481, ServerTransport$ChildObserver (reactor.netty.transport)
onInboundNext:652, HttpServerOperations (reactor.netty.http.server)
channelRead:114, ChannelOperationsHandler (reactor.netty.channel)
invokeChannelRead:444, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
channelRead:238, HttpTrafficHandler (reactor.netty.http.server)
invokeChannelRead:442, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:436, CombinedChannelDuplexHandler$DelegatingChannelHandlerContext (io.netty.channel)
fireChannelRead:346, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:318, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:251, CombinedChannelDuplexHandler (io.netty.channel)
invokeChannelRead:442, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:412, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1410, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:440, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:420, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:919, DefaultChannelPipeline (io.netty.channel)
read:166, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:788, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:724, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:650, NioEventLoop (io.netty.channel.nio)
run:562, NioEventLoop (io.netty.channel.nio)
run:997, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:840, Thread (java.lang)

In my minimal repo when it hits the lambda$wrap$5:565, ObservationWebFilterChainDecorator$WebFilterObservation$SimpleWebFilterObservation (org.springframework.security.web.server) the security's context is there, in my real app it is empty.

@marcusdacoregio marcusdacoregio added status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 6, 2023
@marcusdacoregio
Copy link
Contributor

Hi, everyone. I've created a minimal sample but I had no success reproducing the problem. Can someone apply the needed changes to make the problem reproducible?

@marcusdacoregio marcusdacoregio added the status: waiting-for-feedback We need additional information before we can continue label Dec 6, 2023
@alex-arana
Copy link

I had a similar problem running tests in my Spring Webflux application which stopped working after upgrading from 3.1.5 to 3.2.0. I tracked down the problem to the following condition which was added to org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration (ecc6707):

@ConditionalOnMissingClass({ "org.springframework.security.oauth2.client.registration.ClientRegistrationRepository",
		"org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector" })

In the application properties for my tests I DO configure an OAuth2 client registration repository purely for the purposes of establishing client connections to external APIs.

As a workaround, I added the missing MapReactiveUserDetailsService bean to my test Configuration and everything worked as it did before this change:

https://github.com/spring-projects/spring-boot/blob/fc1a5033e829d903fd2e37989918b1ea202508e4/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/security/reactive/ReactiveUserDetailsServiceAutoConfiguration.java#L78-L83

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 6, 2023
@andrashatvani
Copy link
Author

As a workaround, I added the missing MapReactiveUserDetailsService bean to my test Configuration and everything worked as it did before this change:

@alex-arana Is it different from how I defined my custom bean in the bug description?

@marcusdacoregio marcusdacoregio added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Dec 7, 2023
@andrashatvani
Copy link
Author

andrashatvani commented Dec 11, 2023

@marcusdacoregio I've created another sample project since our basics are so different e.g. I use Maven. You can find it at https://github.com/andrashatvani/sb32sec . Hope it helps.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 11, 2023
@marcusdacoregio
Copy link
Contributor

Thanks for the sample @andrashatvani. It took me a while to make it minimal and runnable, but I found some clues about the problem. I changed my sample to reproduce the error.

The error is only triggered when we do http.csrf(ServerHttpSecurity.CsrfSpec::disable), if you comment that line the test works. I am not sure if this is a problem in Spring Security yet because if using Spring Boot 3.1.6 and Spring Security 6.2.0, the problem does not happen. I'll keep you updated with my findings.

@marcusdacoregio marcusdacoregio removed the status: feedback-provided Feedback has been provided label Dec 13, 2023
@jesperancinha
Copy link

Thanks for the sample @andrashatvani. It took me a while to make it minimal and runnable, but I found some clues about the problem. I changed my sample to reproduce the error.

The error is only triggered when we do http.csrf(ServerHttpSecurity.CsrfSpec::disable), if you comment that line the test works. I am not sure if this is a problem in Spring Security yet because if using Spring Boot 3.1.6 and Spring Security 6.2.0, the problem does not happen. I'll keep you updated with my findings.

This happens to me too in one of my projects. I have also solve it like that. I'm also not sure if letting csrf with its default value is ok.

@marcusdacoregio
Copy link
Contributor

Hi, folks. Unfortunately, I haven't found the root cause yet, however, I can provide a simple workaround so you can update your applications:

http.addFilterAt(new TestWebFilter(), SecurityWebFiltersOrder.CSRF);

// workaround for https://github.com/spring-projects/spring-security/issues/14207
static class TestWebFilter implements WebFilter {

	@Override
	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
		return Mono.empty().switchIfEmpty(chain.filter(exchange)).then();
	}

}

Some newest finding points out that there is something related to the Reactor Core and I'll talk with someone from their team early next week.

@marcusdacoregio marcusdacoregio added the in: test An issue in spring-security-test label Dec 18, 2023
@andrashatvani
Copy link
Author

@marcusdacoregio Unfortunately the workaround is not feasible for us as in the project the security configuration needs to remain untouched even in the integration tests.

chemicL added a commit to reactor/reactor-core that referenced this issue Dec 20, 2023
Recent improvements in the automatic context propagation (#3549)
resulted in a regression – some sites where the "last operator" hook
was previously applied no longer saw that behaviour. This change
restores it.

Implementation wise, it's worth noting that the "last operator"
functionality relies on executing the subscribe(Subscriber) method from
the base reactive-streams Publisher instead of the overloads that come
from CorePublisher. The implementations of the reactive-streams base
method in reactor-core apply this hook and that is when something is
considered a "last operator". The wrapping of the Publisher when a
non-internal producer is encountered to restore ThreadLocal values has
changed the compiler's inference of the signature to use the
CoreSubscriber argument variant, breaking the behaviour.

This commit does not bring any tests as the functionality was not
extensively tested before. The issue was discovered in spring-security
in spring-projects/spring-security#14207 and
the change has been validated against the actual use case.
@marcusdacoregio
Copy link
Contributor

Hi, everyone. Thanks to the help of @chemicL we managed identify a regression that happened in reactor-core 3.6.0. The PR with the fix is available here.

As soon as reactor core 3.6.2 is available, Spring Security will update to it and I may close this issue if the fix is confirmed. Spring Security 6.2.2 is scheduled for Feb 19th, however, when reactor-core is out you can override its dependency version if you want.

@chemicL
Copy link
Member

chemicL commented Dec 20, 2023

The issue is caused by a regression introduced in reactor-core 3.6.0 and the PR linked above should bring back the previous behaviour.

In the PR I expressed an opinion that the onLastOperatorHook functionality should probably be deprecated. From my understanding, it was added to improve ThreadLocal propagation across Thread boundaries. It might have been added specifically for Spring Cloud Sleuth which performs tracing instrumentation. onLastOperatorHook has less execution points than the onEachOperatorHook to reduce the performance impact, but requires additional hooks to improve the reliability (onScheduleHook + queue wrapper hook) and still has its shortcomings. Starting from reactor-core 3.5.0 we introduced better mechanisms for propagation of ThreadLocal state.

The issue with onLastOperatorHook can be understood better with a blog post series we published on the subject – part 2 discusses this hook.

The latest-and-greatest context propagation features are described in our documentation.

chemicL added a commit to reactor/reactor-core that referenced this issue Jan 2, 2024
Recent improvements in the automatic context propagation (#3549)
resulted in a regression – some sites where the "last operator" hook was
previously applied no longer saw that behaviour. This change restores
it.

Implementation wise, it's worth noting that the "last operator"
functionality relies on executing the subscribe(Subscriber) method from
the base reactive-streams Publisher instead of the overloads that come
from CorePublisher. The implementations of the reactive-streams base
method in reactor-core apply this hook and that is when something is
considered a "last operator". The wrapping of the Publisher when a
non-internal producer is encountered to restore ThreadLocal values has
changed the compiler's inference of the signature to use the
CoreSubscriber argument variant, breaking the behaviour.

This commit does not bring any tests as the functionality was not
extensively tested before. The issue was discovered in spring-security
in spring-projects/spring-security#14207 and
the change has been validated against the actual use case.
@andrashatvani
Copy link
Author

With reactor-core 3.6.2 all tests pass indeed.

@chemicL
Copy link
Member

chemicL commented Jan 10, 2024

@andrashatvani thanks for checking it so quickly and letting us know :)

@marcusdacoregio
Copy link
Contributor

Closed via reactor/reactor-core#3673

@marcusdacoregio marcusdacoregio added this to the 6.2.2 milestone Jan 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test An issue in spring-security-test type: bug A general bug
Projects
Status: No status
Development

No branches or pull requests

8 participants