Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MicroProfile JWT application works with Payara Micro, Thorntail, TomEE and fails with Quarkus #3596

Closed
Karm opened this issue Aug 20, 2019 · 3 comments · Fixed by #3601
Closed
Labels
kind/bug Something isn't working
Milestone

Comments

@Karm
Copy link
Member

Karm commented Aug 20, 2019

Hello,

I am working on adding Quarkus runtime to https://start.microprofile.io/ project generator, MicroProfile Starter issue 195, Quarkus issue 1310. I seem to be having a weird issue with our JWT example that fails with Quarkus unless Java code is modified. I do realize Quarkus is neither MP22 nor MP30 TCK compliant at the time of writing. For the propose of the exercise, I work with MP22 as seen below.

I will show an example that works and then I break it by removing Quarkus specific hack.

Generate and build the starter example

Quarkus

  • curl -O -J 'https://starter.karms.biz/api/project?mpVersion=MP22&supportedServer=QUARKUS&selectedSpecs=JWT_AUTH'
  • unzip demo.zip -d q_jwt
  • Split into two terminals, one for service-a and the other for service-b
  • cd q_jwt/demo/service-a
  • mvn clean compile quarkus:build && java -jar target/demo-runner.jar
  • cd q_jwt/demo/service-b
  • mvn clean compile quarkus:build && java -Dquarkus.http.port=8180 -jar target/demo-runner.jar
  • curl -i http://localhost:8080/data/secured/test

Claim value within JWT of 'custom-value' : Protected Resource; Custom value : "Jessie specific value"

That is the expected outcome ✔️. Service A acts as a client to Service B and the contents of the claim is "Jessie specific value".

Thorntail

  • rm -rf demo.zip
  • curl -O -J 'https://starter.karms.biz/api/project?mpVersion=MP22&supportedServer=THORNTAIL_V2&selectedSpecs=JWT_AUTH'
  • unzip demo.zip -d t_jwt
  • cd t_jwt/demo/service-a
  • mvn clean package && java -jar target/demo-thorntail.jar
  • cd t_jwt/demo/service-b
  • mvn clean package && java -jar target/demo-thorntail.jar -Dswarm.port.offset=100
  • curl -i http://localhost:8080/data/secured/test

Claim value within JWT of 'custom-value' : Protected Resource; Custom value : Jessie specific value

Both Thorntail and Quarkus work ✔️ . The problem is the Java code had to be adapted for Quarkus with a hack...

See the difference between classes that are supposed to be the same:

diff q_jwt/demo/service-b/src/main/java/com/example/demo/secure/ProtectedController.java t_jwt/demo/service-b/src/main/java/com/example/demo/secure/ProtectedController.java

Pertinent part:

<         return "Protected Resource; Custom value : " + (Object) custom.getValue();
---
>         return "Protected Resource; Custom value : " + custom.getValue();

Quarkus cannot cast org.glassfish.json.JsonStringImpl to java.lang.String, hence the (Object) cast.

Let's copy Thorntail code to Quarkus and run it again:

Quarkus fails ❌

  • mv q_jwt/demo/service-b/src/main/java/com/example/demo/secure/ProtectedController.java q_jwt/demo/service-b/src/main/java/com/example/demo/secure/ProtectedController.java.backup
  • cp t_jwt/demo/service-b/src/main/java/com/example/demo/secure/ProtectedController.java q_jwt/demo/service-b/src/main/java/com/example/demo/secure/ProtectedController.java

And we run Quarkus example again:

  • cd q_jwt/demo/service-a
  • mvn clean compile quarkus:build && java -jar target/demo-runner.jar
  • cd q_jwt/demo/service-b
  • mvn clean compile quarkus:build && java -Dquarkus.http.port=8180 -jar target/demo-runner.jar
  • curl -i http://localhost:8080/data/secured/test
    <h1 class="container">Internal Server Error</h1>
    <div class="exception-message">
        <h2 class="container">java.lang.ClassCastException: org.glassfish.json.JsonStringImpl cannot be cast to java.base/java.lang.StringError id 91aa7964-4a51-4087-8070-11794337b74c-1</h2>
    </div>

Logs:

2019-08-20 14:30:11,842 INFO  [io.quarkus] (main) Quarkus 0.20.0 started in 0.784s. Listening on: http://[::]:8180
2019-08-20 14:30:11,865 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, security, smallrye-jwt]
2019-08-20 14:30:20,699 ERROR [io.und.req.io] (executor-thread-1) Exception handling request 91aa7964-4a51-4087-8070-11794337b74c-1 to /data/protected: org.jboss.resteasy.spi.UnhandledException: java.lang.ClassCastException: org.glassfish.json.JsonStringImpl cannot be cast to java.base/java.lang.String
    at org.jboss.resteasy.core.ExceptionHandler.handleApplicationException(ExceptionHandler.java:106)
    at org.jboss.resteasy.core.ExceptionHandler.handleException(ExceptionHandler.java:372)
    at org.jboss.resteasy.core.SynchronousDispatcher.writeException(SynchronousDispatcher.java:209)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:496)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:60)
    at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:55)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:791)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:62)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.quarkus.elytron.security.runtime.SecurityContextPrincipalHandler.handleRequest(SecurityContextPrincipalHandler.java:24)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:132)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:64)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:292)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:138)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:135)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$8$1$1.call(UndertowDeploymentRecorder.java:489)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:272)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:81)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:104)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:364)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:830)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.base/java.lang.Thread.run(Thread.java:831)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)
Caused by: java.lang.ClassCastException: org.glassfish.json.JsonStringImpl cannot be cast to java.base/java.lang.String
    at com.example.demo.secure.ProtectedController.getJWTBasedValue(ProtectedController.java:26)
    at com.example.demo.secure.ProtectedController_ClientProxy.getJWTBasedValue(ProtectedController_ClientProxy.zig:54)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:569)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:151)
    at org.jboss.resteasy.core.MethodInjectorImpl.lambda$invoke$3(MethodInjectorImpl.java:122)
    at java.base/java.util.concurrent.CompletableFuture.uniApplyNow(CompletableFuture.java:680)
    at java.base/java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:658)
    at java.base/java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:2094)
    at java.base/java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:143)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:122)
    at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:580)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:454)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:408)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:410)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:379)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invoke$1(ResourceMethodInvoker.java:353)
    at java.base/java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:1106)
    at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2235)
    at java.base/java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:143)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:353)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
    ... 44 more

It is noteworthy, that Thorntail's code works with TomEE and Payara Micro while it fails with Quarkus.

Ideas? Hints? Thoughts on the JWT example app?

@Karm Karm added the kind/bug Something isn't working label Aug 20, 2019
@sberyozkin
Copy link
Member

sberyozkin commented Aug 20, 2019

Hi @Karm I think I may know what is going on, or at least it rings a bell :-).
The code in the smallrye/smallrye-jwt does not wrap custom claims whose values are strings into JsonValue.String to meet the TCK requirements (at the moment only enforced at the Thorntail level), there is a faulty TCK test which expects a String, while Quarkus does, we discussed with @starksm64 and agreed it was better for Quarkus to stay the spec text compliant even though the TCK would not be happy (the test in question, org.eclipse.microprofile.jwt.tck.container.jaxrs.ClaimValueInjectionTest#verifyInjectedCustomString would have to be skipped).
So, if the example accesses a claim which is unknown per the MP JWT terminology, and expects a String, then there will be a class cast exception.
To be honest, I think this wrapping of Strings into JSON value is not really intuitive, but the spec wants it, and so Quarkus does it. But if Quarkus is the only implementation out there which actually does it then may be we can drop this code for now, or make this wrapping optional.

Do you think it explains the above failures ?

@sberyozkin
Copy link
Member

sberyozkin commented Aug 20, 2019

@Karm to be honest we can just drop the Quarkus code enforcing the wrapping of unknown claim Strings and worry about it if the TCK will ever try to force it, looks like no one is actually expecting JsonValue where a String is just a natural fit

@Karm
Copy link
Member Author

Karm commented Aug 20, 2019

THX @sberyozkin

I can remove the Quarkus specific hack and share the same code with all other servers 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants