micronaut-projects / micronaut-security

The official Micronaut security solution
Apache License 2.0
170 stars 127 forks source link

Subtypes of Authentication are not injected as expected #1430

Open Spikhalskiy opened 1 year ago

Spikhalskiy commented 1 year ago

Expected Behavior

The following works correctly and method gets access to an instance of our own class TokenAuthentication


data class TokenAuthentication(
    private val token: Token,
) : io.micronaut.security.authentication.Authentication {
}

@Secured(SecurityRule.IS_AUTHENTICATED)
@Controller("/some")
class SomeController {
    @Post("/{var1}/method")
    fun method(
            @PathVariable var1: String,
            @Body request: SomeOurRequest,
            userToken: io.micronaut.security.authentication.Authentication,
        ): HttpResponse<Unit>
}

But it requires an ugly cast in every method. We expect that

@Post("/{var1}/method")
fun method(
        @PathVariable var1: String,
        @Body request: SomeOurRequest,
        userToken: our.package.TokenAuthentication,
    ): HttpResponse<Unit>

will also work and save us from Authentication -> TokenAuthentication cast.

Actual Behaviour

It looks like that if a custom subclass of io.micronaut.security.authentication.Authentication is used, Micronaut doesn't pick it up as a Authentication and tries to deserialize body of the request into this object the second time.

17:21:39.605 [default-nioEventLoopGroup-4-4] ERROR i.m.http.server.RouteExecutor - Unexpected error occurred: Already claimed
java.lang.IllegalStateException: Already claimed
    at io.micronaut.http.server.netty.body.ManagedBody.checkUnclaimed(ManagedBody.java:76)
    at io.micronaut.http.server.netty.body.ManagedBody.prepareClaim(ManagedBody.java:70)
    at io.micronaut.http.server.netty.body.ImmediateByteBody.processSingle(ImmediateByteBody.java:124)
    at io.micronaut.http.server.netty.binders.NettyBodyAnnotationBinder.transform(NettyBodyAnnotationBinder.java:145)
    at io.micronaut.http.server.netty.binders.NettyBodyAnnotationBinder$1.lambda$new$0(NettyBodyAnnotationBinder.java:101)
    at io.micronaut.core.execution.ImperativeExecutionFlowImpl.flatMap(ImperativeExecutionFlowImpl.java:72)
    at io.micronaut.http.server.netty.binders.NettyBodyAnnotationBinder$1.<init>(NettyBodyAnnotationBinder.java:99)
    at io.micronaut.http.server.netty.binders.NettyBodyAnnotationBinder.bindFullBody(NettyBodyAnnotationBinder.java:92)
    at io.micronaut.http.server.netty.binders.NettyBodyAnnotationBinder.bindFullBodyConvertibleValues(NettyBodyAnnotationBinder.java:74)
    at io.micronaut.http.bind.binders.DefaultBodyAnnotationBinder.bindBodyPart(DefaultBodyAnnotationBinder.java:84)
    at io.micronaut.http.server.netty.binders.NettyBodyAnnotationBinder.bindBodyPart(NettyBodyAnnotationBinder.java:60)
    at io.micronaut.http.bind.binders.DefaultBodyAnnotationBinder.bind(DefaultBodyAnnotationBinder.java:68)
    at io.micronaut.http.bind.binders.DefaultBodyAnnotationBinder.bind(DefaultBodyAnnotationBinder.java:35)
    at io.micronaut.http.bind.binders.DefaultUnmatchedRequestArgumentBinder.bind(DefaultUnmatchedRequestArgumentBinder.java:71)
    at io.micronaut.http.bind.binders.DefaultUnmatchedRequestArgumentBinder.bind(DefaultUnmatchedRequestArgumentBinder.java:35)
    at io.micronaut.web.router.AbstractRouteMatch.fulfillValue(AbstractRouteMatch.java:358)
    at io.micronaut.web.router.AbstractRouteMatch.fulfillBeforeFilters(AbstractRouteMatch.java:314)
    at io.micronaut.http.server.binding.RequestArgumentSatisfier.fulfillArgumentRequirementsBeforeFilters(RequestArgumentSatisfier.java:57)
    at io.micronaut.http.server.netty.NettyRequestArgumentSatisfier.fulfillArgumentRequirementsBeforeFilters(NettyRequestArgumentSatisfier.java:50)
    at io.micronaut.http.server.RequestLifecycle.fulfillArguments(RequestLifecycle.java:417)
    at io.micronaut.http.server.netty.NettyRequestLifecycle.fulfillArguments(NettyRequestLifecycle.java:121)
    at io.micronaut.http.server.RequestLifecycle.lambda$normalFlow$4(RequestLifecycle.java:145)
    at io.micronaut.http.server.RequestLifecycle.lambda$runWithFilters$14(RequestLifecycle.java:264)
    at io.micronaut.http.filter.FilterRunner.processRequestFilter(FilterRunner.java:306)
    at io.micronaut.http.filter.FilterRunner.filterRequest0(FilterRunner.java:183)
    at io.micronaut.http.filter.FilterRunner.lambda$filterRequest0$3(FilterRunner.java:183)
    at io.micronaut.http.filter.FilterRunner$FilterChainImpl.proceed(FilterRunner.java:970)
    at io.micronaut.security.filters.SecurityFilter.lambda$checkRules$7(SecurityFilter.java:164)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onNext(MonoFlatMapMany.java:163)
    at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
    at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerNext(FluxConcatMapNoPrefetch.java:258)
    at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onNext(FluxConcatMap.java:863)
    at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onNext(MonoPeekTerminal.java:180)
    at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
    at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2545)
    at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.request(FluxFilterFuseable.java:411)
    at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.set(Operators.java:2341)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.onSubscribe(Operators.java:2215)
    at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onSubscribe(MonoPeekTerminal.java:152)
    at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onSubscribe(FluxFilterFuseable.java:305)
    at reactor.core.publisher.MonoJust.subscribe(MonoJust.java:55)
    at reactor.core.publisher.Mono.subscribe(Mono.java:4495)
    at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onNext(FluxConcatMapNoPrefetch.java:206)
    at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:335)
    at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:294)
    at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.innerComplete(FluxConcatMapNoPrefetch.java:274)
    at reactor.core.publisher.FluxConcatMap$ConcatMapInner.onComplete(FluxConcatMap.java:887)
    at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.onComplete(MonoPeekTerminal.java:299)
    at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onComplete(FluxFilterFuseable.java:391)
    at reactor.core.publisher.Operators$ScalarSubscription.request(Operators.java:2547)
    at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.request(FluxFilterFuseable.java:411)
    at reactor.core.publisher.MonoPeekTerminal$MonoTerminalPeekSubscriber.request(MonoPeekTerminal.java:139)
    at reactor.core.publisher.Operators$MultiSubscriptionSubscriber.request(Operators.java:2305)
    at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.request(FluxConcatMapNoPrefetch.java:338)
    at reactor.core.publisher.MonoNext$NextSubscriber.request(MonoNext.java:108)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyMain.onSubscribe(MonoFlatMapMany.java:141)
    at reactor.core.publisher.MonoNext$NextSubscriber.onSubscribe(MonoNext.java:70)
    at reactor.core.publisher.FluxConcatMapNoPrefetch$FluxConcatMapNoPrefetchSubscriber.onSubscribe(FluxConcatMapNoPrefetch.java:164)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83)
    at reactor.core.publisher.MonoFromFluxOperator.subscribe(MonoFromFluxOperator.java:81)
    at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:165)
    at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
    at reactor.core.publisher.FluxFlatMap$FlatMapMain.tryEmit(FluxFlatMap.java:544)
    at reactor.core.publisher.FluxFlatMap$FlatMapInner.onNext(FluxFlatMap.java:985)
    at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:122)
    at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:82)
    at reactor.core.publisher.FluxFlatMap$FlatMapMain.tryEmitScalar(FluxFlatMap.java:489)
    at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:422)
    at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:335)
    at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:294)
    at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:371)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83)
    at reactor.core.publisher.Mono.subscribe(Mono.java:4495)
    at reactor.core.publisher.FluxFlatMap$FlatMapMain.onNext(FluxFlatMap.java:427)
    at reactor.core.publisher.FluxIterable$IterableSubscription.slowPath(FluxIterable.java:335)
    at reactor.core.publisher.FluxIterable$IterableSubscription.request(FluxIterable.java:294)
    at reactor.core.publisher.FluxFlatMap$FlatMapMain.onSubscribe(FluxFlatMap.java:371)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:201)
    at reactor.core.publisher.FluxIterable.subscribe(FluxIterable.java:83)
    at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
    at io.micronaut.core.async.propagation.ReactivePropagation$1.subscribe(ReactivePropagation.java:54)
    at io.micronaut.core.async.propagation.ReactivePropagation$1.subscribe(ReactivePropagation.java:61)
    at reactor.core.publisher.MonoFromPublisher.subscribe(MonoFromPublisher.java:67)
    at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
    at io.micronaut.core.async.propagation.ReactivePropagation$1.subscribe(ReactivePropagation.java:54)
    at io.micronaut.core.async.propagation.ReactivePropagation$1.subscribe(ReactivePropagation.java:61)
    at reactor.core.publisher.MonoFromPublisher.subscribe(MonoFromPublisher.java:67)
    at reactor.core.publisher.InternalMonoOperator.subscribe(InternalMonoOperator.java:64)
    at io.micronaut.http.reactive.execution.ReactorExecutionFlowImpl.onComplete(ReactorExecutionFlowImpl.java:89)
    at io.micronaut.http.server.netty.NettyRequestLifecycle.handleNormal(NettyRequestLifecycle.java:87)
    at io.micronaut.http.server.netty.RoutingInBoundHandler.accept(RoutingInBoundHandler.java:220)
    at io.micronaut.http.server.netty.websocket.NettyServerWebSocketUpgradeHandler.accept(NettyServerWebSocketUpgradeHandler.java:156)
    at io.micronaut.http.server.netty.handler.PipeliningServerHandler$OptimisticBufferingInboundHandler.read(PipeliningServerHandler.java:422)
    at io.micronaut.http.server.netty.handler.PipeliningServerHandler.channelRead(PipeliningServerHandler.java:206)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
    at io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler.channelRead(WebSocketServerExtensionHandler.java:88)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
    at io.netty.handler.codec.MessageToMessageCodec.channelRead(MessageToMessageCodec.java:111)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.ChannelInboundHandlerAdapter.channelRead(ChannelInboundHandlerAdapter.java:93)
    at io.netty.handler.codec.http.HttpServerKeepAliveHandler.channelRead(HttpServerKeepAliveHandler.java:64)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.micronaut.http.server.netty.handler.accesslog.HttpAccessLogHandler.channelRead(HttpAccessLogHandler.java:143)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
    at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
    at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:286)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:833)

Version

platform: 4.0.5

Spikhalskiy commented 1 year ago

I was able to solve it by registering

@Singleton
class TokenAuthenticationArgumentBinder : AbstractPrincipalArgumentBinder< TokenAuthentication >(
    TokenAuthentication::class.java,
)

but it feels like it shouldn't have to be done explicitly.

Spikhalskiy commented 1 year ago

If such a binder is really expected to be present by design, it would be great if the error message could be improved to point out on the fact that there is no applicable RequestArgumentBinder found for the class.

sdelamo commented 1 year ago

Yes, I think the creation of the RequestArgumentBinder is required. we should improve the docs.

Spikhalskiy commented 1 year ago

Also, it would be great if an error message could be replaced with something meaningful. If you know netty, it's clear what happens here - we try to use the body a second time. But for a lot of users, it may be a very obscure exception.