Skip to content

WebClient + OAuth 2.0 client - token isn't saved #7880

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

Closed
altro3 opened this issue Jan 30, 2020 · 4 comments
Closed

WebClient + OAuth 2.0 client - token isn't saved #7880

altro3 opened this issue Jan 30, 2020 · 4 comments
Assignees
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue

Comments

@altro3
Copy link

altro3 commented Jan 30, 2020

Summary

I wrote a client for PayPal, they use OAuth 2.0 for security. I decided to try applying your solution to webFlux WebClient. Your solution works and is quite convenient to use. But, the problem is that the received token is not saved and is requested from the server with each request.

Take a look at the example below. The logs show that before each request, another request is added, which is sent to the server in order to get the token, although the request for the token should be sent only before the first request to the server and the previously received token should be used for the next requests.

Actual Behavior

The token is not stored in memory and a request for a token is sent before each request to the server

Expected Behavior

The request for the token will occur only 1 time and the next time the token will be requested when it expires

Configuration

See example

Version

SpringBoot 2.2.3 and "org.springframework.security:spring-security-oauth2-client:5.2.1.RELEASE"

Sample

This is my sample:

@Configuration
public class PaypalConfig {

    public static final String PAYPAL_REGISTRATION_ID = "paypal-client";

    @Bean
    public ReactiveClientRegistrationRepository clientRegistrationRepository() {
        var clientRegistration = ClientRegistration.withRegistrationId(PAYPAL_REGISTRATION_ID)
                .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
                .clientId("<client_id>")
                .clientSecret("<secret>")
                .tokenUri("<token_uri>")
                .build();
        return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
    }

    @Bean
    public WebClient paypalWebClient(ReactiveClientRegistrationRepository clientRegistrationRepo) {

        var oauth = new ServerOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepo,  new UnAuthenticatedServerOAuth2AuthorizedClientRepository());
        oauth.setDefaultClientRegistrationId(PAYPAL_REGISTRATION_ID);

        return WebClient.builder()
                .baseUrl("https://api.sandbox.paypal.com")
                .filter(oauth)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
    }
}

Service

@Service
public class PaypalClient {

    private final WebClient webClient;

    public PaypalClient(@Qualifier("paypalWebClient") WebClient webClient) {
        this.webClient = webClient;
    }

    public ProductsListResponse listProducts() {
        return webClient
                .method(HttpMethod.GET)
                .uri("/v1/catalogs/products")
                .exchange()
                .flatMap(response -> response.bodyToMono(ProductsListResponse.class))
                .block();
    }
}

And Test

@ActiveProfiles("test")
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
        PaypalConfig.class,
        PaypalClientTest.TestConfig.class,
}, initializers = ConfigFileApplicationContextInitializer.class)
class PaypalClientTest {

    @Autowired
    PaypalClient paypalClient;

    @Test
    void listProducts() {
        paypalClient.listProducts();
        paypalClient.listProducts();
    }

    @TestConfiguration
    static class TestConfig {

        @Bean
        public PaypalClient paypalClient(@Qualifier("paypalWebClient") WebClient paypalWebClient) {
            return new PaypalClient(paypalWebClient);
        }
    }

}

And what you can see in logs:

00:57:34.282 DEBUG [PaypalClient.java:113] Send request to paypal LIST_PRODUCTS
00:57:34.670 TRACE [LogFormatUtils.java:88] [2450256f] HTTP POST https://api.sandbox.paypal.com/v1/oauth2/token, headers={masked}
00:57:36.478 DEBUG [CompositeLog.java:147] [2450256f] Writing form fields [grant_type] (content masked)
00:57:36.806 TRACE [LogFormatUtils.java:88] [2450256f] Response 200 OK, headers={masked}
00:57:36.866 TRACE [CompositeLog.java:157] [2450256f] Decoded [{scope=https://uri.paypal.com/services/invoicing https://uri.paypal.com/services/disputes/read-buyer https://uri.paypal.com/services/payments/realtimepayment https://uri.paypal.com/services/disputes/update-seller https://uri.paypal.com/services/payments/payment/authcapture openid https://uri.paypal.com/services/disputes/read-seller https://uri.paypal.com/services/payments/refund https://api.paypal.com/v1/vault/credit-card https://api.paypal.com/v1/payments/.* https://uri.paypal.com/payments/payouts https://api.paypal.com/v1/vault/credit-card/.* https://uri.paypal.com/services/subscriptions https://uri.paypal.com/services/applications/webhooks, access_token=A21AAH8zT4emhWBup00bHl6DqZ6VbSl-p2QC42UsWA_5c8NTteCOEI2ZeqSz4QgDFDve5a7Q3-wPl7F1dODOZVi3l9HuggfOA, token_type=Bearer, app_id=APP-80W284485P519543T, expires_in=29440, nonce=2020-01-30T17:08:17Z8QTXwzD6A-p2ZDr5cHNjgFThaRV7gU7vHjJve22jClc}]
00:57:36.882 DEBUG [ExchangeFunctions.java:106] [2450256f] Cancel signal (to close connection)
00:57:36.887 TRACE [LogFormatUtils.java:88] [6c07ad6b] HTTP GET https://api.sandbox.paypal.com/v1/catalogs/products, headers=[Content-Type:"application/json", Authorization:"Bearer A21AAH8zT4emhWBup00bHl6DqZ6VbSl-p2QC42UsWA_5c8NTteCOEI2ZeqSz4QgDFDve5a7Q3-wPl7F1dODOZVi3l9HuggfOA"]
00:57:37.804 TRACE [LogFormatUtils.java:88] [6c07ad6b] Response 200 OK, headers=[Cache-Control:"max-age=0, no-cache, no-store, must-revalidate", Content-Length:"3078", Content-Type:"application/json", Date:"Thu, 30 Jan 2020 17:57:38 GMT", Paypal-Debug-Id:"1ace1d74b202d"]
00:57:37.876 TRACE [CompositeLog.java:157] [6c07ad6b] Decoded [ProductsListResponse...
00:57:37.876 DEBUG [PaypalClient.java:113] Send request to paypal LIST_PRODUCTS
00:57:37.878 TRACE [LogFormatUtils.java:88] [2086d469] HTTP POST https://api.sandbox.paypal.com/v1/oauth2/token, headers={masked}
00:57:37.879 DEBUG [CompositeLog.java:147] [2086d469] Writing form fields [grant_type] (content masked)
00:57:38.197 TRACE [LogFormatUtils.java:88] [2086d469] Response 200 OK, headers={masked}
00:57:38.198 TRACE [CompositeLog.java:157] [2086d469] Decoded [{scope=https://uri.paypal.com/services/invoicing https://uri.paypal.com/services/disputes/read-buyer https://uri.paypal.com/services/payments/realtimepayment https://uri.paypal.com/services/disputes/update-seller https://uri.paypal.com/services/payments/payment/authcapture openid https://uri.paypal.com/services/disputes/read-seller https://uri.paypal.com/services/payments/refund https://api.paypal.com/v1/vault/credit-card https://api.paypal.com/v1/payments/.* https://uri.paypal.com/payments/payouts https://api.paypal.com/v1/vault/credit-card/.* https://uri.paypal.com/services/subscriptions https://uri.paypal.com/services/applications/webhooks, access_token=A21AAH8zT4emhWBup00bHl6DqZ6VbSl-p2QC42UsWA_5c8NTteCOEI2ZeqSz4QgDFDve5a7Q3-wPl7F1dODOZVi3l9HuggfOA, token_type=Bearer, app_id=APP-80W284485P519543T, expires_in=29439, nonce=2020-01-30T17:08:17Z8QTXwzD6A-p2ZDr5cHNjgFThaRV7gU7vHjJve22jClc}]
00:57:38.198 DEBUG [ExchangeFunctions.java:106] [2086d469] Cancel signal (to close connection)
00:57:38.199 TRACE [LogFormatUtils.java:88] [521ba38f] HTTP GET https://api.sandbox.paypal.com/v1/catalogs/products, headers=[Content-Type:"application/json", Authorization:"Bearer A21AAH8zT4emhWBup00bHl6DqZ6VbSl-p2QC42UsWA_5c8NTteCOEI2ZeqSz4QgDFDve5a7Q3-wPl7F1dODOZVi3l9HuggfOA"]
00:57:38.578 TRACE [LogFormatUtils.java:88] [521ba38f] Response 200 OK, headers=[Cache-Control:"max-age=0, no-cache, no-store, must-revalidate", Content-Length:"3078", Content-Type:"application/json", Date:"Thu, 30 Jan 2020 17:57:39 GMT", Paypal-Debug-Id:"20d0651c73c7b"]
00:57:38.581 TRACE [CompositeLog.java:157] [521ba38f] Decoded [ProductsListResponse...
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 30, 2020
@jgrandja
Copy link
Contributor

jgrandja commented Feb 3, 2020

@altro3 I believe the issue is related to your configuration as the previously fetched token should be reused on subsequent requests. This feature has been working for quite some time now.

The best approach to determine the issue you are having is if you could provide a minimal sample that reproduces the issue. I can then troubleshoot from the sample and provide you a fix.

@jgrandja jgrandja added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: waiting-for-feedback We need additional information before we can continue and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 3, 2020
@jgrandja jgrandja changed the title [Bug] WebClient + OAuth 2.0 client - token isn't saved WebClient + OAuth 2.0 client - token isn't saved Feb 3, 2020
@jgrandja jgrandja self-assigned this Feb 3, 2020
@altro3
Copy link
Author

altro3 commented Feb 6, 2020

@jgrandja Hi! Thanks for your answer. I created sample project here: https://github.com/altro3/spring-oauth-test

For test you can use any OAuth 2.0 server, I used paypal OAuth for test.

To run test use this command

.\gradlew clean build -i

You need to change these settings before run test:

class org.sping.oauthtest.OauthConfig

                .clientId("")
                .clientSecret("")
                .tokenUri("https://api.sandbox.paypal.com/v1/oauth2/token")

Class org.sping.oauthtest.OauthClient

                .method(HttpMethod.GET)
                .uri("https://api.sandbox.paypal.com/v1/catalogs/products")

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Feb 6, 2020
@jgrandja
Copy link
Contributor

Thanks for providing the sample @altro3.

This bug was originally reported in #7544 and a fix was applied in 5.2.2 via #7684.

Upgrade to 5.2.2 and it will work. I'll close this issue since you now have a solution.

@jgrandja jgrandja added status: duplicate A duplicate of another issue and removed status: feedback-provided Feedback has been provided labels Feb 10, 2020
@altro3
Copy link
Author

altro3 commented Feb 11, 2020

@jgrandja Yes, it works fine in with version 5.2.2. Thanks for help! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

3 participants