vert-x3 / vertx-web

HTTP web applications for Vert.x
Apache License 2.0
1.11k stars 535 forks source link

ChainAuthHandlerImpl#postAuthentication throws IndexOutOfBoundsException #2641

Closed andrei-tulba closed 2 months ago

andrei-tulba commented 2 months ago

Version

4.5.9

Context

This commit https://github.com/vert-x3/vertx-web/commit/f8565123f6130725af93dc94fae55d85d5033dce causes an IndexOutOfBoundsException when calling the postAuthentication method in nested chain auth hadlers.

Do you have a reproducer?

ChainAuthHandlerImplTest

[!TIP] Cast an eye on README for pre-requisites and installation guide.

Steps to reproduce

./mvnw -Dtest=org.vaaron.vertx.web.handler.ChainAuthHandlerImplTest test

Extra

Linked issues

Stacktrace

java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)
    at java.base/java.util.Objects.checkIndex(Objects.java:385)
    at java.base/java.util.ArrayList.get(ArrayList.java:427)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.postAuthentication(ChainAuthHandlerImpl.java:144)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.postAuthentication(ChainAuthHandlerImpl.java:144)
    at io.vertx.ext.web.handler.impl.AuthenticationHandlerImpl.lambda$handle$0(AuthenticationHandlerImpl.java:98)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.lambda$iterate$0(ChainAuthHandlerImpl.java:116)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.lambda$iterate$0(ChainAuthHandlerImpl.java:116)
    at io.vertx.ext.web.handler.impl.SimpleAuthenticationHandlerImpl.lambda$authenticate$1(SimpleAuthenticationHandlerImpl.java:32)
    at io.vertx.core.impl.future.SucceededFuture.onSuccess(SucceededFuture.java:64)
    at io.vertx.ext.web.handler.impl.SimpleAuthenticationHandlerImpl.authenticate(SimpleAuthenticationHandlerImpl.java:32)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.iterate(ChainAuthHandlerImpl.java:83)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.authenticate(ChainAuthHandlerImpl.java:59)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.iterate(ChainAuthHandlerImpl.java:83)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.lambda$iterate$0(ChainAuthHandlerImpl.java:99)
    at io.vertx.ext.web.handler.impl.SimpleAuthenticationHandlerImpl.lambda$authenticate$0(SimpleAuthenticationHandlerImpl.java:29)
    at io.vertx.core.impl.future.FailedFuture.onFailure(FailedFuture.java:91)
    at io.vertx.ext.web.handler.impl.SimpleAuthenticationHandlerImpl.authenticate(SimpleAuthenticationHandlerImpl.java:25)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.iterate(ChainAuthHandlerImpl.java:83)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.lambda$iterate$0(ChainAuthHandlerImpl.java:99)
    at io.vertx.ext.web.handler.impl.SimpleAuthenticationHandlerImpl.lambda$authenticate$0(SimpleAuthenticationHandlerImpl.java:29)
    at io.vertx.core.impl.future.FailedFuture.onFailure(FailedFuture.java:91)
    at io.vertx.ext.web.handler.impl.SimpleAuthenticationHandlerImpl.authenticate(SimpleAuthenticationHandlerImpl.java:25)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.iterate(ChainAuthHandlerImpl.java:83)
    at io.vertx.ext.web.handler.impl.ChainAuthHandlerImpl.authenticate(ChainAuthHandlerImpl.java:59)
    at io.vertx.ext.web.handler.impl.AuthenticationHandlerImpl.handle(AuthenticationHandlerImpl.java:84)
    at io.vertx.ext.web.handler.impl.AuthenticationHandlerImpl.handle(AuthenticationHandlerImpl.java:31)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1285)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:140)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl.handle(BodyHandlerImpl.java:95)
    at io.vertx.ext.web.handler.impl.BodyHandlerImpl.handle(BodyHandlerImpl.java:45)
    at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1285)
    at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:177)
    at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:140)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:68)
    at io.vertx.ext.web.impl.RouterImpl.handle(RouterImpl.java:37)
    at io.vertx.core.http.impl.Http1xServerRequestHandler.handle(Http1xServerRequestHandler.java:67)
    at io.vertx.core.http.impl.Http1xServerRequestHandler.handle(Http1xServerRequestHandler.java:30)
    at io.vertx.core.impl.ContextImpl.emit(ContextImpl.java:328)
    at io.vertx.core.impl.DuplicatedContext.emit(DuplicatedContext.java:164)
    at io.vertx.core.http.impl.Http1xServerConnection.handleMessage(Http1xServerConnection.java:174)
    at io.vertx.core.net.impl.ConnectionBase.read(ConnectionBase.java:159)
    at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:153)
    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.websocketx.extensions.WebSocketServerExtensionHandler.onHttpRequestChannelRead(WebSocketServerExtensionHandler.java:158)
    at io.netty.handler.codec.http.websocketx.extensions.WebSocketServerExtensionHandler.channelRead(WebSocketServerExtensionHandler.java:82)
    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.vertx.core.http.impl.Http1xUpgradeToH2CHandler.channelRead(Http1xUpgradeToH2CHandler.java:124)
    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.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346)
    at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318)
    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.vertx.core.http.impl.Http1xOrH2CHandler.end(Http1xOrH2CHandler.java:61)
    at io.vertx.core.http.impl.Http1xOrH2CHandler.channelRead(Http1xOrH2CHandler.java:38)
    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.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407)
    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:918)
    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:994)
    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:1583)
tsegismont commented 2 months ago

Thanks for the reproducer. Can you elaborate about your use case in production? I understand it is possible to create a configuration that fails as such, but I'm not sure yet why nested chains are needed.

andrei-tulba commented 2 months ago
  1. JWT Types: There are three types of JSON Web Tokens (JWTs) used in our production code. Each type is processed through a series of handlers.

  2. 3rd Type of JWT: The third type of JWT includes user claims. Sometimes, there's an additional, optional token that represents a more privileged user. This additional token is sent as a custom header and is managed by a separate handler. Here we wrap into an all chain handler these 2 handlers

  3. Integration Testing: For integration testing, we use a static token of the third type JWT. In this context chain handler from bullet 2 get wrapped into another any chain handler in addition with a simple authentication handler (checks a static test JWT).

Production schema:

Integration tests:

tsegismont commented 2 months ago

Thanks for the details @andrei-tulba

tsegismont commented 2 months ago

Fixed by ae4177281