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

[Enahncement request] [spring-boot-starter-oauth2-client] ability to provide custom RestClient instead of using RestTemplate for provider get token observablity #16389

Closed
patpatpat123 opened this issue Jan 9, 2025 · 9 comments
Assignees
Labels
for: stackoverflow A question that's better suited to stackoverflow.com in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)

Comments

@patpatpat123
Copy link
Contributor

patpatpat123 commented Jan 9, 2025

Context

How has this issue affected you?
Without this enhancement request, we are blind in production for issues getting the token using spring-boot-starter-oauth2-client

What are you trying to accomplish?
Observability for the call to get the token

What other alternatives have you considered?
Are you aware of any workarounds?
Dropping spring-boot-starter-oauth2-client in favor of writing our own two calls with the RestClient, which we would like to avoid (we like spring-boot-starter-oauth2-client)

Current Behavior

Here is our code:

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager, RestClient.Builder restClientBuilder) {
        OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        return restClientBuilder
                .requestInterceptor(interceptor)
                .build();
    }
@RestController
public class LessonsController {

    private final RestClient restClient;

    public LessonsController(RestClient restClient) {
        this.restClient = restClient;
    }

    @GetMapping("/lessons")
    public String fetchLessons() {
        return restClient.get()
                .uri("https://someprotectedresourceneedingtoken")
                .attributes(clientRegistrationId("my-client"))
                .retrieve()
                .body(String.class);
    }
}
spring:
  application:
    name: client-application
  security:
    oauth2:
      client:
        registration:
          my-client:
            provider: the-provider
            client-id: someusername
            client-secret: somepassword
            authorization-grant-type: client_credentials
            scope:
              - resolve
              - download
        provider:
          ssa-provider:
            token-uri: https://somethirdparty.com/token
logging:
  level:
    root: DEBUG

With this code, we would get the following observation. (the get part is important)
Screenshot 2025-01-10 at 00 10 13

2025-01-09T23:59:01.308+08:00 DEBUG 4250 --- [client-application] [nio-8080-exec-2] [29e0fac8ee9435452ddc0e91ac67b652-9c95bddc85f879bb] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@10ce6bfd 8 pairs: {POST /token HTTP/1.1: null}{Accept: application/json;charset=UTF-8}{Content-Type: application/x-www-form-urlencoded;charset=UTF-8}{...5}{User-Agent: Java/23}{Host: ....com}{Connection: keep-alive}{Content-Length: 76}
2025-01-09T23:59:01.420+08:00 DEBUG 4250 --- [client-application] [nio-8080-exec-2] [29e0fac8ee9435452ddc0e91ac67b652-9c95bddc85f879bb] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@67f01113 13 pairs: {null: HTTP/1.1 200}{Date: Thu, 09 Jan 2025 15:59:01 GMT}{Content-Type: application/json;charset=UTF-8}{Transfer-Encoding: chunked}{Connection: keep-alive}{Vary: origin,access-control-request-method,access-control-request-headers,accept-encoding}{X-Content-Type-Options: nosniff}{X-XSS-Protection: 0}{Cache-Control: no-cache, no-store, max-age=0, must-revalidate}{Pragma: no-cache}{Expires: 0}{Strict-Transport-Security: max-age=31536000 ; includeSubDomains}{X-Frame-Options: DENY}
2025-01-09T23:59:01.420+08:00 DEBUG 4250 --- [client-application] [nio-8080-exec-2] [29e0fac8ee9435452ddc0e91ac67b652-9c95bddc85f879bb] o.s.web.client.RestTemplate              : Response 200 OK
2025-01-09T23:59:01.421+08:00 DEBUG 4250 --- [client-application] [nio-8080-exec-2] [29e0fac8ee9435452ddc0e91ac67b652-9c95bddc85f879bb] o.s.web.client.RestTemplate              : Reading to [org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse] as "application/json;charset=UTF-8"

Unfortunately, the first call to get the token is missing. We can only see the call to get the protected resource.

This is a drawback, the service providing the token is sometimes flaky. Overall, it is a fair requirement to have observability on the call to get the token as well.

Expected Behavior

We are hoping to see something like this:

Screenshot 2025-01-10 at 00 06 19

This is achievable by not using spring-boot-starter-oauth2-client and writing our own code:

 @GetMapping("/lessons")
    public String fetchLessons() {
        Map ssa = restClient.post()
                .uri("https://service.com/token?scope=resolve+download&grant_type=client_credentials")
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .headers(httpHeaders -> httpHeaders.setBasicAuth("aaa", "bbb"))
                .retrieve()
                .body(Map.class);

        final MultiValueMap<String, String> params2 = new LinkedMultiValueMap<>();
        params2.add("Authorization", "Bearer " + ssa.get("access_token"));
        Consumer<HttpHeaders> consumer2 = it -> it.addAll(params2);

        String result = restClient.get()
                .uri("https://protectedresource...")
                .headers(consumer2)
                .retrieve()
                .body(String.class);
        return result;
    }

But we like spring-boot-starter-oauth2-client and would like to avoid this route.

Would it be possible to enhance spring-boot-starter-oauth2-client, allowing it to pass in the custom RestClient? (not RestTemplate, not WebClient)

Rationale:

Many users already have a custom RestClient in their class path. It is properly configured with observability, SSL certificate, and so on.

Instead of defaulting the call to get the token with RestTemplate (which does not show any observability), it would be great if the user could pass the existing restClient.

Thank you for your time

@patpatpat123 patpatpat123 added status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Jan 9, 2025
@patpatpat123 patpatpat123 changed the title [Enahncement reuqest] [spring-boot-starter-oauth2-client] ability to provide custom RestClient instead of using RestTemplate for provider get token observablity [Enahncement request] [spring-boot-starter-oauth2-client] ability to provide custom RestClient instead of using RestTemplate for provider get token observablity Jan 13, 2025
@patpatpat123
Copy link
Contributor Author

Hello team, is it possible to get some help on this?

@jzheaux jzheaux added in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose) and removed status: waiting-for-triage An issue we've not yet triaged type: enhancement A general enhancement labels Feb 4, 2025
@jzheaux jzheaux self-assigned this Feb 4, 2025
@jzheaux
Copy link
Contributor

jzheaux commented Feb 4, 2025

Hi, @patpatpat123, as far as I know what you are asking for is already possible: https://docs.spring.io/spring-security/reference/servlet/oauth2/client/authorization-grants.html#oauth2-client-authorization-code-access-token

Am I misunderstanding?

@jzheaux jzheaux added the status: waiting-for-feedback We need additional information before we can continue label Feb 4, 2025
@patpatpat123
Copy link
Contributor Author

patpatpat123 commented Feb 5, 2025

Hello @jzheaux ,

Wanted to thank you for your time looking into this.

I saw the guide.

With either

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager, RestClient.Builder restClientBuilder) {
        OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        return restClientBuilder
                .requestInterceptor(interceptor)
                .build();
    }

or

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager, RestClient.Builder restClientBuilder) {
        OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        return restClientBuilder
                .requestInterceptor(interceptor)
                .build();
    }

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
        return new RestClientAuthorizationCodeTokenResponseClient();
    }

}

or

@Configuration
public class RestClientConfig {

    @Bean
    public RestClient restClient(OAuth2AuthorizedClientManager authorizedClientManager, RestClient.Builder restClientBuilder) {
        OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
        return restClientBuilder
                .requestInterceptor(interceptor)
                .build();
    }

    @Bean
    public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient(RestClient restClient) {
        RestClientAuthorizationCodeTokenResponseClient accessTokenResponseClient =
                new RestClientAuthorizationCodeTokenResponseClient();
        accessTokenResponseClient.setRestClient(restClient);
        return accessTokenResponseClient;
    }

}

I am still seeing the use of RestTemplate

2025-02-05T18:43:32.360+08:00 DEBUG 5133 --- [client-application] [nio-8080-exec-2] [f69e232008fd4b2812539fe93a3b02f6-65c05f4d29a7ff08] o.s.web.client.RestTemplate              : HTTP POST https://abc.com/token
2025-02-05T18:43:32.360+08:00 DEBUG 5133 --- [client-application] [nio-8080-exec-2] [f69e232008fd4b2812539fe93a3b02f6-65c05f4d29a7ff08] o.s.web.client.RestTemplate              : Accept=[application/json, application/*+json]
2025-02-05T18:43:32.360+08:00 DEBUG 5133 --- [client-application] [nio-8080-exec-2] [f69e232008fd4b2812539fe93a3b02f6-65c05f4d29a7ff08] o.s.web.client.RestTemplate              : Writing [{grant_type=[client_credentials], scope=[download]}] as "application/x-www-form-urlencoded;charset=UTF-8"

I would have expected to see the use of the rest client instead of the rest template.
Am I wrong?

I can see the setRestClient() method suggested by the doc is being called.

But I would have expected the same RestClient provided, which has observability configured would get triggered.
(Something like seeing the trace for line 79?)

Image

Also, just wanted to say, I think there is a typo in the doc (please see screenshot):

Image

Could you please help by letting me know if I did something wrong in my RestClientConfig class?

Thank you again

@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 5, 2025
@sjohnr sjohnr assigned sjohnr and unassigned jzheaux Feb 5, 2025
@sjohnr
Copy link
Member

sjohnr commented Feb 5, 2025

Hi @patpatpat123, thanks for reaching out. As @jzheaux mentioned, this is well supported through the example linked in the comment. The second configuration you provided is correct, the others are not. I have double-checked that this configuration works. At this point, I am not able to reproduce your finding.

Could you please provide a minimal, reproducible sample or ask a question on Stack Overflow with the same?

@sjohnr sjohnr added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Feb 5, 2025
@sjohnr
Copy link
Member

sjohnr commented Feb 5, 2025

Also, just wanted to say, I think there is a typo in the doc (please see screenshot):

It is a typo, yes. Would you like to submit a PR to fix the typo?

@patpatpat123
Copy link
Contributor Author

Sure @jzheaux ,

It is not a big deal actually.

On the file spring-security/docs/modules/ROOT/partials/servlet/oauth2/client/rest-client-access-token-response-client.adoc

line 260,

This:

=== Customizing the `WebClient`

Should be:

=== Customizing the `RestClient`

@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, 2025
@sjohnr
Copy link
Member

sjohnr commented Feb 6, 2025

Thanks @patpatpat123, please feel free to submit a PR.

Did you see this comment?

Could you please provide a minimal, reproducible sample or ask a question on Stack Overflow with the same?

@sjohnr sjohnr added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Feb 6, 2025
@patpatpat123
Copy link
Contributor Author

#16562

@patpatpat123
Copy link
Contributor Author

I am having a hard time making it minimal, as it requires us to mock a token provider, a resource server, an observability platform, etc...

With that said, the destination would be stackoverflow.

Therefore, closing this, and attached the PR for review.

Thank you

@sjohnr sjohnr added status: invalid An issue that we don't feel is valid and removed status: waiting-for-feedback We need additional information before we can continue labels Feb 10, 2025
@sjohnr sjohnr added for: stackoverflow A question that's better suited to stackoverflow.com and removed status: invalid An issue that we don't feel is valid labels Feb 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
for: stackoverflow A question that's better suited to stackoverflow.com in: oauth2 An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)
Projects
None yet
Development

No branches or pull requests

4 participants