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)
@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