SAP / cloud-sdk-java

Use the SAP Cloud SDK for Java to reduce development effort when building applications on SAP Business Technology Platform that communicate with SAP solutions and services such as SAP S/4HANA Cloud, SAP SuccessFactors, and many others.
Apache License 2.0
22 stars 13 forks source link

OAuth2JWTBearer regarded as for "User Token Exchange" #588

Closed jingweiz2017 closed 2 months ago

jingweiz2017 commented 2 months ago

Issue Description

We had one destination configured under the subscriber's subaccount with its Authentication property set to 'OAuth2JWTBearer'. Once our application obtained that destination and used it for accessing a remote service, the Cloud SDK will try to do UserTokenExchange by first obtaining an auth token. Based on my understand, a destination with Authentication being 'OAuth2JWTBearer' doesn't require UserTokenExchange, but should use TECHNICAL_USER_CURRENT_TENANT or TECHNICAL_USER_PROVIDER instead depend on occasion.

Root Cause Analysis: After our application get the destination, the cloud sdk take “Authentication: OAuth2JWTBearer” in destination as a criteria to determine that this destination needs “user token exchange” (DestinationRetrievalStrategyResolver::doesDestinationConfigurationRequireUserTokenExchange). Because of that, it assign NAMED_USER_CURRENT_TENANT as token retrieve strategy which leads to dwc-jwt/dwc-ias-jwt auth token retrieval(DestinationRetrievalStrategyResolver #196). For OAuth2JWTBearer, it should go with TECHNICAL_USER_CURRENT_TENANT or TECHNICAL_USER_PROVIDER instead. I attached a screen shot of the destination, as well.

Function “DestinationRetrievalStrategyResolver::doesDestinationConfigurationRequireUserTokenExchange” return true on OAuth2JWTBearer is not correct. It should return false, so that behalfTechnicalUser(DestinationRetrievalStrategyResolver::resolveSingleRequestStrategy #78) get a chance to kick in.

Fix: I also prepared a fix for this issue based on the analysis above. Please let me know when to submit the PR.

Important information:

Impact / Priority

Affected development phase: Development

Impact: Blocked

Timeline: Not Specified

Error Message

"Caused by: com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException: Failed to get destination.",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService.resilientCall(DestinationService.java: 377)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService.lambda$loadAndParseDestination$1(DestinationService.java: 137)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationRetrievalStrategyResolver.lambda$prepareSupplier$5(DestinationRetrievalStrategyResolver.java: 196)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationRetrieval.get(DestinationRetrieval.java: 26)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService.loadAndParseDestination(DestinationService.java: 145)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.GetOrComputeSingleDestinationCommand.lambda$prepareCommand$0(GetOrComputeSingleDestinationCommand.java: 70)",
        "\tat io.vavr.control.Try.of(Try.java: 75)",
        "\tat io.vavr.control.Try.ofSupplier(Try.java: 92)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.GetOrComputeSingleDestinationCommand.execute(GetOrComputeSingleDestinationCommand.java: 157)",
        "\tat io.vavr.control.Try.flatMapTry(Try.java: 490)",
        "\tat io.vavr.control.Try.flatMap(Try.java: 472)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService$Cache.getOrComputeDestination(DestinationService.java: 873)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService.tryGetDestination(DestinationService.java: 128)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationLoaderChain.tryGetDestination(DestinationLoaderChain.java: 89)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationLoader.tryGetDestination(DestinationLoader.java: 37)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor.tryGetDestination(DestinationAccessor.java: 124)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor.getDestination(DestinationAccessor.java: 101)",
        ...
        "\tat com.sap.cds.services.impl.handlerregistry.HandlerRegistryTools$DescribedHandler.process(HandlerRegistryTools.java: 165)",
        "\tat com.sap.cds.services.impl.ServiceImpl.lambda$createOnHandlerChain$4(ServiceImpl.java: 269)",
        "\tat com.sap.cds.services.impl.ServiceImpl.dispatch(ServiceImpl.java: 236)",
        "\t... 168 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.resilience.ResilienceRuntimeException: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException: Header provider 'OAuth2HeaderProvider' threw an exception: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException: Failed to get the current user token.",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateCallable$3(Resilience4jDecorationStrategy.java: 195)",
        "\tat io.vavr.control.Try.onFailure(Try.java: 659)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateCallable$4(Resilience4jDecorationStrategy.java: 194)",
        "\tat io.vavr.control.Try.of(Try.java: 75)",
        "\tat io.vavr.control.Try.ofCallable(Try.java: 105)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateSupplier$1(Resilience4jDecorationStrategy.java: 160)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorationStrategy.executeSupplier(ResilienceDecorationStrategy.java: 103)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorator.executeSupplier(ResilienceDecorator.java: 201)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService.resilientCall(DestinationService.java: 371)",
        "\t... 189 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException: Header provider 'OAuth2HeaderProvider' threw an exception: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException: Failed to get the current user token.",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.execute(ThreadContextExecutor.java: 246)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.DefaultThreadContextExecutorService.lambda$decorate$0(DefaultThreadContextExecutorService.java: 72)",
        "\tat com.sap.cds.integration.cloudsdk.decorator.CdsThreadContextDecorator.lambda$decorateCallable$0(CdsThreadContextDecorator.java: 36)",
        "\tat com.sap.cds.services.impl.runtime.RequestContextRunnerImpl.run(RequestContextRunnerImpl.java: 272)",
        "\tat com.sap.cds.integration.cloudsdk.decorator.CdsThreadContextDecorator.lambda$decorateCallable$1(CdsThreadContextDecorator.java: 34)",
        ...
        "\tat java.base/java.util.concurrent.FutureTask.run(Unknown Source)",
        "\tat java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)",
        "\tat java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)",
        "\t... 1 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.connectivity.exception.DestinationAccessException: Header provider 'OAuth2HeaderProvider' threw an exception: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException: Failed to get the current user token.",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination.getHeadersFromHeaderProviders(DefaultHttpDestination.java: 207)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination.getHeaders(DefaultHttpDestination.java: 176)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientWrapper.wrapRequest(HttpClientWrapper.java: 125)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientWrapper.execute(HttpClientWrapper.java: 143)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.HttpClientWrapper.execute(HttpClientWrapper.java: 37)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationServiceAdapter.getConfigurationAsJson(DestinationServiceAdapter.java: 138)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService.retrieveDestination(DestinationService.java: 152)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DestinationService.lambda$loadAndParseDestination$0(DestinationService.java: 137)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateCallable$2(Resilience4jDecorationStrategy.java: 179)",
        "\tat io.github.resilience4j.bulkhead.Bulkhead.lambda$decorateCallable$5(Bulkhead.java: 173)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.call(ThreadContextExecutor.java: 297)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.execute(ThreadContextExecutor.java: 240)",
        "\t... 9 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.resilience.ResilienceRuntimeException: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException: Failed to get the current user token.",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateCallable$3(Resilience4jDecorationStrategy.java: 195)",
        "\tat io.vavr.control.Try.onFailure(Try.java: 659)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateCallable$4(Resilience4jDecorationStrategy.java: 194)",
        "\tat io.vavr.control.Try.of(Try.java: 75)",
        "\tat io.vavr.control.Try.ofCallable(Try.java: 105)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateSupplier$1(Resilience4jDecorationStrategy.java: 160)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorationStrategy.executeSupplier(ResilienceDecorationStrategy.java: 103)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience.ResilienceDecorator.executeSupplier(ResilienceDecorator.java: 201)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2Service.retrieveAccessToken(OAuth2Service.java: 140)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2HeaderProvider.getHeaders(OAuth2HeaderProvider.java: 34)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.DefaultHttpDestination.getHeadersFromHeaderProviders(DefaultHttpDestination.java: 201)",
        "\t... 20 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.thread.exception.ThreadContextExecutionException: com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException: Failed to get the current user token.",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.execute(ThreadContextExecutor.java: 246)",
        "\t... 9 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.exception.CloudPlatformException: Failed to get the current user token.",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2Service.executeUserExchangeFlow(OAuth2Service.java: 253)",
        "\tat com.sap.cloud.sdk.cloudplatform.connectivity.OAuth2Service.lambda$retrieveAccessToken$0(OAuth2Service.java: 148)",
        "\tat com.sap.cloud.sdk.cloudplatform.resilience4j.Resilience4jDecorationStrategy.lambda$decorateCallable$2(Resilience4jDecorationStrategy.java: 179)",
        "\tat io.github.resilience4j.bulkhead.Bulkhead.lambda$decorateCallable$5(Bulkhead.java: 173)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.call(ThreadContextExecutor.java: 297)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.execute(ThreadContextExecutor.java: 240)",
        "\t... 9 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.security.exception.AuthTokenAccessException: Failed to extract auth token from DwC headers.",
        "\tat com.sap.cloud.sdk.cloudplatform.security.DwcAuthTokenFacade.extractAuthTokenFromDwcHeaders(DwcAuthTokenFacade.java: 43)",
        "\tat io.vavr.control.Try.of(Try.java: 75)",
        "\tat com.sap.cloud.sdk.cloudplatform.security.DwcAuthTokenFacade.tryGetCurrentToken(DwcAuthTokenFacade.java: 32)",
        "\tat com.sap.cloud.sdk.cloudplatform.security.AuthTokenAccessor.tryGetCurrentToken(AuthTokenAccessor.java: 123)",
        "\tat com.sap.cloud.sdk.cloudplatform.security.AuthTokenAccessor.getCurrentToken(AuthTokenAccessor.java: 104)",
        "\tat io.vavr.control.Try.of(Try.java: 75)",
        "\tat io.vavr.control.Try.ofCallable(Try.java: 105)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.Property.lambda$decorateCallable$1(Property.java: 135)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContext.setPropertyIfAbsent(ThreadContext.java: 70)",
        "\tat com.sap.cloud.sdk.cloudplatform.security.AuthTokenThreadContextListener.afterInitialize(AuthTokenThreadContextListener.java: 63)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.notifyAfterInitialize(ThreadContextExecutor.java: 322)",
        "\tat com.sap.cloud.sdk.cloudplatform.thread.ThreadContextExecutor.call(ThreadContextExecutor.java: 295)",
        "\t... 10 more",
        "Caused by: com.sap.cloud.sdk.cloudplatform.exception.DwcHeaderNotFoundException: Unable to find the dwc-jwt or dwc-ias-jwt in header.",
        "\tat com.sap.cloud.sdk.cloudplatform.DwcHeaderUtils.getDwcJwtOrThrow(DwcHeaderUtils.java: 130)",
        "\tat com.sap.cloud.sdk.cloudplatform.security.DwcAuthTokenFacade.extractAuthTokenFromDwcHeaders(DwcAuthTokenFacade.java: 39)",
        "\t... 21 more"

We have upgrade to Cloud SDK 5.12

Project Details

Internal project and disclosure is not allowed.


Checklist

jingweiz2017 commented 2 months ago

From this document, I understood OAuth2JWTBearer indeed requires a user token exchange. Can you help to explain "why ClientID and ClientSecret are still needed in destination with authentication being OAuth2JWTBearer?"

CharlesDuboisSAP commented 2 months ago

The OAuth2JWTBearer needs both the credentials and the user information to create the JwT. Therefore the destination needs the dwc-jwt or dwc-ias-jwt in the header.

_See: grant_type of JWT token_

jingweiz2017 commented 2 months ago

Thanks for the sharing.