quarkus: smallrye jwt returns Response despite exception being caught

Describe the bug

I have a simple quarkus reactive resteasy server

@Path("/")
@ApplicationScoped
public class Hello {

    @GET
    @Path("hello")
    @RolesAllowed({"roleYouNeed"})
    @Blocking
    public Uni<Response> hello(){
        return Uni.createFrom().item(Response.ok("hello").build());
    }
}
        

Following the documentation found here I create several exception mappers to return a response body which elaborates on the status code. This one for example works when you do not provide a JWT token to a request that needs it:

@Provider//register as JAXRS provider
@Priority(1)//override the build-int mapper(which has 5001 prio)
@Slf4j
public class UnauthorizedExceptionMapper implements ExceptionMapper<UnauthorizedException> {
    @Override
    public Response toResponse(UnauthorizedException exception) {
        log.warn("There was an incoming request that did not have the correct Authorization");
        return Response.status(Response.Status.UNAUTHORIZED)
                .entity(new ErrorResponseObject().addErrorsItem(new ErrorResponseObjectErrorsInner()
                        .code(String.valueOf(Response.Status.UNAUTHORIZED.getStatusCode()))
                        .detail("Unauthorized")))
                .build();
    }

}

This works as expected on my endpoint that uses quarkus-smallrye-jwt with the annotation @RolesAllowed({"roleYouNeed"})

When someone sends a token that is truly worthless however a JWTParse Exception occurs and a different exception is raised, the AuthenticationFailedException

To catch this one I created

@Provider
@Priority(1)
@Slf4j
public class AuthenticationExceptionMapper implements ExceptionMapper<AuthenticationFailedException> {
    @Override
    public Response toResponse(AuthenticationFailedException exception) {
        log.warn("There was an incoming request that did not have the correct Authorization");
        return Response.status(Response.Status.UNAUTHORIZED)
                .entity(new ErrorResponseObject().addErrorsItem(new ErrorResponseObjectErrorsInner()
                        .code(String.valueOf(Response.Status.UNAUTHORIZED.getStatusCode()))
                        .detail("Unauthorized")))
                .build();
    }
}

This does catch the exception and my warning is logged: [com.ene.odp.exc.AuthenticationExceptionMapper] (vert.x-eventloop-thread-0) There was an incoming request that did not have the correct Authorization . The problem here is that a response has already been returned and I see the following error occurring:

2022-07-25 16:22:03,812 ERROR [org.jbo.res.rea.com.cor.AbstractResteasyReactiveContext] (vert.x-eventloop-thread-1) Request failed: java.io.IOException: java.io.IOException: Failed to write
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveOutputStream.close(ResteasyReactiveOutputStream.java:272)
        at io.quarkus.resteasy.reactive.jackson.runtime.serialisers.BasicServerJacksonMessageBodyWriter.writeResponse(BasicServerJacksonMessageBodyWriter.java:44)
        at org.jboss.resteasy.reactive.server.core.ServerSerialisers.invokeWriter(ServerSerialisers.java:207)
        at org.jboss.resteasy.reactive.server.core.serialization.DynamicEntityWriter.write(DynamicEntityWriter.java:104)
        at org.jboss.resteasy.reactive.server.handlers.ResponseWriterHandler.handle(ResponseWriterHandler.java:31)
        at org.jboss.resteasy.reactive.server.handlers.ResponseWriterHandler.handle(ResponseWriterHandler.java:15)
        at org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext.run(AbstractResteasyReactiveContext.java:141)
        at org.jboss.resteasy.reactive.server.vertx.VertxResteasyReactiveRequestContext$1$1.handle(VertxResteasyReactiveRequestContext.java:73)
        at org.jboss.resteasy.reactive.server.vertx.VertxResteasyReactiveRequestContext$1$1.handle(VertxResteasyReactiveRequestContext.java:70)
        at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:100)
        at io.vertx.core.impl.AbstractContext.dispatch(AbstractContext.java:63)
        at io.vertx.core.impl.EventLoopContext.lambda$runOnContext$0(EventLoopContext.java:38)
        at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
        at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:469)
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:503)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
        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)
Caused by: java.io.IOException: Failed to write
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveOutputStream.write(ResteasyReactiveOutputStream.java:109)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveOutputStream.writeBlocking(ResteasyReactiveOutputStream.java:224)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveOutputStream.close(ResteasyReactiveOutputStream.java:270)
        ... 18 more
Caused by: java.lang.IllegalStateException: Response has already been written
        at io.vertx.core.http.impl.Http1xServerResponse.checkValid(Http1xServerResponse.java:675)
        at io.vertx.core.http.impl.Http1xServerResponse.writeQueueFull(Http1xServerResponse.java:274)
        at io.quarkus.vertx.http.runtime.filters.AbstractResponseWrapper.writeQueueFull(AbstractResponseWrapper.java:486)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveOutputStream.awaitWriteable(ResteasyReactiveOutputStream.java:116)
        at org.jboss.resteasy.reactive.server.vertx.ResteasyReactiveOutputStream.write(ResteasyReactiveOutputStream.java:82)

Why is the response already being returned while I catch this exception? How can I make sure nothing is returned except what I return from the AuthenticationExceptionMapper?

Expected behavior

When I catch the exception in my AuthenticationExceptionMapper no response is returned except one that I return from the mapper.

Actual behavior

The smallrye-jwt extension returns a 401 before I can return an adequate response from my exception mapper

How to Reproduce?

Create a simple server with an endpoint that uses the smallrye-jwt extension. Create an extension mapper that catches the AuthenticationFailedException and then call the endpoint with some gibberish Authorization: Bearer <Token>.

Output of uname -a or ver

21.0.1 Darwin Kernel Version 21.0.1: Tue Sep 14 20:56:24 PDT 2021; root:xnu-8019.30.61~4/RELEASE_ARM64_T6000 arm64

Output of java -version

OpenJDK 64-Bit Server VM GraalVM CE 22.1.0 (build 17.0.3+7-jvmci-22.1-b06, mixed mode, sharing)

GraalVM version (if different from Java)

No response

Quarkus version or git rev

2.9.0.Final

Build tool (ie. output of mvnw --version or gradlew --version)

apache-maven-3.8.4

Additional information

No response

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Comments: 22 (11 by maintainers)

Most upvoted comments

@michalvavrik I guess I should have read through the releases more carefully. It works now as expected.

Thanks a lot! It’s really appreciated.

Thanks for tagging me @sberyozkin.

@marcvanR if you attach a sample that works with RESTEasy Classic and not Reactive, I can have a look