I have created REST resource with RestEasy reactive in Quarkus. It's expecting a JWT and is protected with this annotation in method:
@RolesAllowed({"read.access"})
I've defined JWKS URL in properties:
mp:
jwt:
verify:
publickey:
location: http://my.server/jwks
The problem is that it is using Java HttpClient to refresh JWKS in a blocking manner which causes this when the reactive REST resource is called:
io.vertx.core.VertxException: Thread blocked
at java.base@11.0.2/java.net.PlainSocketImpl.socketConnect(Native Method)
at java.base@11.0.2/java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:399)
at java.base@11.0.2/java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:242)
at java.base@11.0.2/java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:224)
at java.base@11.0.2/java.net.Socket.connect(Socket.java:591)
at java.base@11.0.2/sun.net.NetworkClient.doConnect(NetworkClient.java:177)
at java.base@11.0.2/sun.net.www.http.HttpClient.openServer(HttpClient.java:474)
at java.base@11.0.2/sun.net.www.http.HttpClient.openServer(HttpClient.java:569)
at java.base@11.0.2/sun.net.www.http.HttpClient.<init>(HttpClient.java:242)
at java.base@11.0.2/sun.net.www.http.HttpClient.New(HttpClient.java:341)
at java.base@11.0.2/sun.net.www.http.HttpClient.New(HttpClient.java:362)
at java.base@11.0.2/sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:1242)
at java.base@11.0.2/sun.net.www.protocol.http.HttpURLConnection.plainConnect0(HttpURLConnection.java:1181)
at java.base@11.0.2/sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:1075)
at java.base@11.0.2/sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:1009)
at java.base@11.0.2/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1581)
at java.base@11.0.2/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1509)
at java.base@11.0.2/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:527)
at org.jose4j.http.Get.get(Get.java:80)
at org.jose4j.jwk.HttpsJwks.refresh(HttpsJwks.java:204)
at io.smallrye.jwt.auth.principal.AbstractKeyLocationResolver.isHttpsJwksInitialized(AbstractKeyLocationResolver.java:100)
at io.smallrye.jwt.auth.principal.KeyLocationResolver.initializeKeyContent(KeyLocationResolver.java:88)
at io.smallrye.jwt.auth.principal.KeyLocationResolver.<init>(KeyLocationResolver.java:44)
at io.smallrye.jwt.auth.principal.DefaultJWTTokenParser.getVerificationKeyResolver(DefaultJWTTokenParser.java:226)
at io.smallrye.jwt.auth.principal.DefaultJWTTokenParser.parseClaims(DefaultJWTTokenParser.java:95)
at io.smallrye.jwt.auth.principal.DefaultJWTTokenParser.parse(DefaultJWTTokenParser.java:56)
at io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipalFactory.parse(DefaultJWTCallerPrincipalFactory.java:31)
at io.smallrye.jwt.auth.principal.DefaultJWTParser.parse(DefaultJWTParser.java:60)
at io.smallrye.jwt.auth.principal.DefaultJWTParser_Subclass.parse$$superaccessor9(DefaultJWTParser_Subclass.zig:1575)
at io.smallrye.jwt.auth.principal.DefaultJWTParser_Subclass$$function$$9.apply(DefaultJWTParser_Subclass$$function$$9.zig:33)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.proceed(AroundInvokeInvocationContext.java:54)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.proceed(InvocationInterceptor.java:63)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor.monitor(InvocationInterceptor.java:51)
at io.quarkus.arc.runtime.devconsole.InvocationInterceptor_Bean.intercept(InvocationInterceptor_Bean.zig:521)
at io.quarkus.arc.impl.InterceptorInvocation.invoke(InterceptorInvocation.java:41)
at io.quarkus.arc.impl.AroundInvokeInvocationContext.perform(AroundInvokeInvocationContext.java:41)
at io.quarkus.arc.impl.InvocationContexts.performAroundInvoke(InvocationContexts.java:32)
at io.smallrye.jwt.auth.principal.DefaultJWTParser_Subclass.parse(DefaultJWTParser_Subclass.zig:1523)
at io.smallrye.jwt.auth.principal.DefaultJWTParser_ClientProxy.parse(DefaultJWTParser_ClientProxy.zig:368)
at io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator$1.accept(MpJwtValidator.java:53)
at io.quarkus.smallrye.jwt.runtime.auth.MpJwtValidator$1.accept(MpJwtValidator.java:49)
at io.smallrye.context.impl.wrappers.SlowContextualConsumer.accept(SlowContextualConsumer.java:21)
at io.smallrye.mutiny.operators.uni.builders.UniCreateWithEmitter.subscribe(UniCreateWithEmitter.java:22)
at io.smallrye.mutiny.operators.AbstractUni.subscribe(AbstractUni.java:36)
at io.smallrye.mutiny.operators.uni.UniMemoizeOp.subscribe(UniMemoizeOp.java:76)
at io.smallrye.mutiny.operators.AbstractUni.subscribe(AbstractUni.java:36)
at io.smallrye.mutiny.groups.UniSubscribe.withSubscriber(UniSubscribe.java:50)
at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$2.handle(HttpSecurityRecorder.java:104)
at io.quarkus.vertx.http.runtime.security.HttpSecurityRecorder$2.handle(HttpSecurityRecorder.java:51)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:137)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:132)
at io.quarkus.micrometer.runtime.binder.vertx.VertxMeterFilter.handle(VertxMeterFilter.java:19)
at io.quarkus.micrometer.runtime.binder.vertx.VertxMeterFilter.handle(VertxMeterFilter.java:14)
at io.vertx.ext.web.impl.RouteState.handleContext(RouteState.java:1038)
at io.vertx.ext.web.impl.RoutingContextImplBase.iterateNext(RoutingContextImplBase.java:137)
at io.vertx.ext.web.impl.RoutingContextImpl.next(RoutingContextImpl.java:132)
at io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup$3.handle(VertxHttpHotReplacementSetup.java:88)
at io.quarkus.vertx.http.runtime.devmode.VertxHttpHotReplacementSetup$3.handle(VertxHttpHotReplacementSetup.java:77)
at io.vertx.core.impl.ContextImpl.lambda$null$0(ContextImpl.java:327)
at io.vertx.core.impl.ContextImpl$$Lambda$766/0x00000008005f8840.handle(Unknown Source)
at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:366)
at io.vertx.core.impl.EventLoopContext.lambda$executeAsync$0(EventLoopContext.java:38)
at io.vertx.core.impl.EventLoopContext$$Lambda$713/0x0000000800588840.run(Unknown Source)
at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:164)
at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:472)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:500)
at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base@11.0.2/java.lang.Thread.run(Thread.java:834)
Because of this I would like the JWKS refresh to happen as a separate scheduled process outside of the reactive request. I haven't found support for this in Quarkus.
Currently I'm planning to create a scheduled task to call the JWKS URL, extract the public key and save it as a file. Then I can point publickey location to that file.
Edit
Tried it but the public key file is cached by Quarkus so refreshing it does not help.
As of now the problem has disappeared and also at some point before the problem did not occur. It looks like it depends on luck if Quarkus decides to refresh the JWKS during a reactive REST resource request or not.
I'm expecting the problem to appear again but let's see.