helidon-io / helidon

Java libraries for writing microservices
https://helidon.io
Apache License 2.0
3.52k stars 564 forks source link

Client call fails with MP JWT #5537

Closed danielkec closed 1 year ago

danielkec commented 1 year ago

Environment Details


Problem Description

When using only MP JWT for securing Helidon MP service with following config:

mp.jwt.verify:
  issuer: "http://${keycloak.host}:${keycloak.port}/realms/helidon-in-action"
  publickey:
    location: ${mp.jwt.verify.issuer}/protocol/openid-connect/certs

It's not possible to use Jersey client.

@Path("/watchtower")
@RequestScoped
@Authenticated
public class WatchtowerResource {
    @POST
    @RolesAllowed({"warden"})
    @Path("/signal")
    public void signal(@Context SecurityContext securityContext, String url) {
        ClientBuilder.newBuilder()
                .build()
                .target(url)
                .path("/comm/ack")
                .request()
                .get();
    }
}
2022.11.25 15:40:28 SEVERE AUDIT Thread[helidon-security-thread-pool-3,5,security-thread-pool]: ERROR outbound.outbound d7a9625e-87f2-4519-a8c3-855917a2c8be:1  io.helidon.common.context.Contexts runInContext Contexts.java 117 :: "Provider io.helidon.microprofile.jwt.auth.JwtAuthProvider, Description io.helidon.security.OutboundSecurityClientImpl@5181afc0, Request java.lang.NullPointerException: Cannot invoke "io.helidon.security.providers.common.OutboundConfig.findTarget(io.helidon.security.SecurityEnvironment)" because "this.outboundConfig" is null. Subject java.util.concurrent.CompletionException: java.lang.NullPointerException: Cannot invoke "io.helidon.security.providers.common.OutboundConfig.findTarget(io.helidon.security.SecurityEnvironment)" because "this.outboundConfig" is null"
2022.11.25 15:40:28 WARNING io.helidon.security.integration.jersey.client.ClientSecurityFilter Thread[helidon-server-1,5,server]: Failed to process client filter.
io.helidon.security.SecurityException: Failure while executing asynchronous security
    at io.helidon.security.SecurityResponse.get(SecurityResponse.java:66)
    at io.helidon.security.SecurityClient.get(SecurityClient.java:46)
    at io.helidon.security.OutboundSecurityClientBuilder.buildAndGet(OutboundSecurityClientBuilder.java:106)
    at io.helidon.security.integration.jersey.client.ClientSecurityFilter.outboundSecurity(ClientSecurityFilter.java:141)
    at io.helidon.security.integration.jersey.client.ClientSecurityFilter.doFilter(ClientSecurityFilter.java:81)
    at io.helidon.security.integration.jersey.client.ClientSecurityFilter.filter(ClientSecurityFilter.java:68)
    at org.glassfish.jersey.client.ClientFilteringStages$RequestFilteringStage.apply(ClientFilteringStages.java:144)
    at org.glassfish.jersey.client.ClientFilteringStages$RequestFilteringStage.apply(ClientFilteringStages.java:132)
    at org.glassfish.jersey.process.internal.Stages.process(Stages.java:147)
    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:297)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$0(JerseyInvocation.java:662)
    at org.glassfish.jersey.client.JerseyInvocation.call(JerseyInvocation.java:697)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$runInScope$3(JerseyInvocation.java:691)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:205)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:390)
    at org.glassfish.jersey.client.JerseyInvocation.runInScope(JerseyInvocation.java:691)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:661)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:413)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:313)
    at io.course.jwt.sec.WatchtowerResource.signal(WatchtowerResource.java:32)
    at io.course.jwt.sec.WatchtowerResource$Proxy$_$$_WeldClientProxy.signal(Unknown Source)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:134)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:177)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$VoidOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:159)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:81)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:478)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:400)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)
    at io.helidon.webserver.jersey.JerseySupport$JerseyHandler.lambda$doAccept$4(JerseySupport.java:334)
    at io.helidon.common.context.Contexts.runInContext(Contexts.java:117)
    at io.helidon.common.context.ContextAwareExecutorImpl.lambda$wrap$7(ContextAwareExecutorImpl.java:154)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.util.concurrent.ExecutionException: io.helidon.security.SecurityException: Failed to process security
    at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
    at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096)
    at io.helidon.security.SecurityResponse.get(SecurityResponse.java:62)
    ... 49 more
Caused by: io.helidon.security.SecurityException: Failed to process security
    at io.helidon.security.OutboundSecurityClientImpl.lambda$submit$1(OutboundSecurityClientImpl.java:96)
    at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:990)
    at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:974)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
    ... 5 more
Caused by: java.util.concurrent.CompletionException: java.lang.NullPointerException: Cannot invoke "io.helidon.security.providers.common.OutboundConfig.findTarget(io.helidon.security.SecurityEnvironment)" because "this.outboundConfig" is null
    at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
    at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1770)
    ... 5 more
Caused by: java.lang.NullPointerException: Cannot invoke "io.helidon.security.providers.common.OutboundConfig.findTarget(io.helidon.security.SecurityEnvironment)" because "this.outboundConfig" is null
    at io.helidon.microprofile.jwt.auth.JwtAuthProvider.lambda$syncOutbound$10(JwtAuthProvider.java:399)
    at java.base/java.util.Optional.flatMap(Optional.java:289)
    at io.helidon.microprofile.jwt.auth.JwtAuthProvider.lambda$syncOutbound$11(JwtAuthProvider.java:398)
    at java.base/java.util.Optional.orElseGet(Optional.java:364)
    at io.helidon.microprofile.jwt.auth.JwtAuthProvider.syncOutbound(JwtAuthProvider.java:390)
    at io.helidon.security.spi.SynchronousProvider.lambda$outboundSecurity$2(SynchronousProvider.java:85)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
    ... 5 more
2022.11.25 15:40:28 WARNING io.helidon.microprofile.server.JaxRsCdiExtension Thread[helidon-server-1,5,server]: Internal server error
jakarta.ws.rs.ProcessingException: Failure while executing asynchronous security
    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:309)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$0(JerseyInvocation.java:662)
    at org.glassfish.jersey.client.JerseyInvocation.call(JerseyInvocation.java:697)
    at org.glassfish.jersey.client.JerseyInvocation.lambda$runInScope$3(JerseyInvocation.java:691)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:205)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:390)
    at org.glassfish.jersey.client.JerseyInvocation.runInScope(JerseyInvocation.java:691)
    at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:661)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:413)
    at org.glassfish.jersey.client.JerseyInvocation$Builder.get(JerseyInvocation.java:313)
    at io.course.jwt.sec.WatchtowerResource.signal(WatchtowerResource.java:32)
    at io.course.jwt.sec.WatchtowerResource$Proxy$_$$_WeldClientProxy.signal(Unknown Source)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.glassfish.jersey.server.model.internal.ResourceMethodInvocationHandlerFactory.lambda$static$0(ResourceMethodInvocationHandlerFactory.java:52)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher$1.run(AbstractJavaResourceMethodDispatcher.java:134)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.invoke(AbstractJavaResourceMethodDispatcher.java:177)
    at org.glassfish.jersey.server.model.internal.JavaResourceMethodDispatcherProvider$VoidOutInvoker.doDispatch(JavaResourceMethodDispatcherProvider.java:159)
    at org.glassfish.jersey.server.model.internal.AbstractJavaResourceMethodDispatcher.dispatch(AbstractJavaResourceMethodDispatcher.java:81)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:478)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:400)
    at org.glassfish.jersey.server.model.ResourceMethodInvoker.apply(ResourceMethodInvoker.java:81)
    at org.glassfish.jersey.server.ServerRuntime$1.run(ServerRuntime.java:255)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:248)
    at org.glassfish.jersey.internal.Errors$1.call(Errors.java:244)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:244)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:265)
    at org.glassfish.jersey.server.ServerRuntime.process(ServerRuntime.java:234)
    at org.glassfish.jersey.server.ApplicationHandler.handle(ApplicationHandler.java:684)
    at io.helidon.webserver.jersey.JerseySupport$JerseyHandler.lambda$doAccept$4(JerseySupport.java:334)
    at io.helidon.common.context.Contexts.runInContext(Contexts.java:117)
    at io.helidon.common.context.ContextAwareExecutorImpl.lambda$wrap$7(ContextAwareExecutorImpl.java:154)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: io.helidon.security.SecurityException: Failure while executing asynchronous security
    at io.helidon.security.SecurityResponse.get(SecurityResponse.java:66)
    at io.helidon.security.SecurityClient.get(SecurityClient.java:46)
    at io.helidon.security.OutboundSecurityClientBuilder.buildAndGet(OutboundSecurityClientBuilder.java:106)
    at io.helidon.security.integration.jersey.client.ClientSecurityFilter.outboundSecurity(ClientSecurityFilter.java:141)
    at io.helidon.security.integration.jersey.client.ClientSecurityFilter.doFilter(ClientSecurityFilter.java:81)
    at io.helidon.security.integration.jersey.client.ClientSecurityFilter.filter(ClientSecurityFilter.java:68)
    at org.glassfish.jersey.client.ClientFilteringStages$RequestFilteringStage.apply(ClientFilteringStages.java:144)
    at org.glassfish.jersey.client.ClientFilteringStages$RequestFilteringStage.apply(ClientFilteringStages.java:132)
    at org.glassfish.jersey.process.internal.Stages.process(Stages.java:147)
    at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:297)
    ... 40 more
Caused by: java.util.concurrent.ExecutionException: io.helidon.security.SecurityException: Failed to process security
    at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:396)
    at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:2096)
    at io.helidon.security.SecurityResponse.get(SecurityResponse.java:62)
    ... 49 more
Caused by: io.helidon.security.SecurityException: Failed to process security
    at io.helidon.security.OutboundSecurityClientImpl.lambda$submit$1(OutboundSecurityClientImpl.java:96)
    at java.base/java.util.concurrent.CompletableFuture.uniExceptionally(CompletableFuture.java:990)
    at java.base/java.util.concurrent.CompletableFuture$UniExceptionally.tryFire(CompletableFuture.java:974)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773)
    ... 5 more
Caused by: java.util.concurrent.CompletionException: java.lang.NullPointerException: Cannot invoke "io.helidon.security.providers.common.OutboundConfig.findTarget(io.helidon.security.SecurityEnvironment)" because "this.outboundConfig" is null
    at java.base/java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:315)
    at java.base/java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:320)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1770)
    ... 5 more
Caused by: java.lang.NullPointerException: Cannot invoke "io.helidon.security.providers.common.OutboundConfig.findTarget(io.helidon.security.SecurityEnvironment)" because "this.outboundConfig" is null
    at io.helidon.microprofile.jwt.auth.JwtAuthProvider.lambda$syncOutbound$10(JwtAuthProvider.java:399)
    at java.base/java.util.Optional.flatMap(Optional.java:289)
    at io.helidon.microprofile.jwt.auth.JwtAuthProvider.lambda$syncOutbound$11(JwtAuthProvider.java:398)
    at java.base/java.util.Optional.orElseGet(Optional.java:364)
    at io.helidon.microprofile.jwt.auth.JwtAuthProvider.syncOutbound(JwtAuthProvider.java:390)
    at io.helidon.security.spi.SynchronousProvider.lambda$outboundSecurity$2(SynchronousProvider.java:85)
    at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
    ... 5 more
danielkec commented 1 year ago

Workaround

#TODO: Remove when https://github.com/helidon-io/helidon/issues/5537 is fixed
security:
  providers:
      - jwt:
          atn-token:
            verify-signature: false
          sign-token:
            outbound:
              - name: "propagate-token"
edthorne commented 1 year ago

I ran into this today when migrating some services to Helidon from another MicroProfile implementation. My workaround was to simply add propagate: false to the application.yaml after reviewing the source to JwtAuthProvider.

After looking at the documentation and the MicroProfile JWT Auth Spec, I'm left with a few questions/observations:

  1. The MP JWT and REST Client specifications do not address JWT propagation. The default behavior of the provider is to enable it without the necessary configuration. This seems counterproductive and leads to the issue. I believe the propagate value should default to false absent the necessary configuration.
  2. The configuration value to disable propagation, and any configuration key not explicitly identified in the MP JWT specification, lacks context. Consider that all the specification config properties begin mp.jwt.*. This security provider is NOT configured under security.providers. Is the placement of these configuration values correct at the root?
  3. Formatting issues aside, the Configuration documentation for outbound security is not easy to understand and no examples are provided. How does the outbound security system work? Is it generating it's own token or working with an external token provider?

I'm working on getting the OCA signed so I can help address these and future issues.