I am trying to use spring-boot-starter-oauth2-client with the new Spring Framework release 6 HttpInterface
@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://someserver.om/someprotectedresource")
.attributes(clientRegistrationId("my-client"))
.retrieve()
.body(String.class);
}
}
spring:
application:
name: client-application
security:
oauth2:
client:
registration:
my-client:
provider: my-provider
client-id: ididid
client-secret: secretsecret
authorization-grant-type: client_credentials
scope: download
provider:
my-provider:
token-uri: https://provider.com/token
The above is working. We have confirmation from the token provider we got the token, as well as from the resource server we got the resource, passing the token. Both two steps are working fine, happy.
https://www.youtube.com/watch?v=aR580OCEp7w We now would like to do the same, with the new Spring Framework release 6 HttpInterface
When doing this:
@Configuration
public class UserClientConfig {
private final RestClient restClient;
public UserClientConfig(OAuth2AuthorizedClientManager authorizedClientManager, RestClient.Builder restClientBuilder) {
OAuth2ClientHttpRequestInterceptor interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
this.restClient = restClientBuilder
.requestInterceptor(interceptor)
.baseUrl("https://host.com")
.build();
}
@Bean
public UserClient userClient() {
RestClientAdapter adapter = RestClientAdapter.create(restClient);
return HttpServiceProxyFactory.builderFor(adapter)
.build()
.createClient(UserClient.class);
}
}
@HttpExchange(
url = "/v1",
accept = MediaType.APPLICATION_JSON_VALUE)
public interface UserClient {
@GetExchange("/protectedresource/full")
public User getUserById(@RequestParam Map<String, String> key value);
}
@GetMapping("/lessons")
public User fetchLessons() {
return userClient.getUserById(Map.of("foo", "bar"));
}
When using HttpInterface, this would not work. The token is not fetched in the first place. Maybe because of the lack of .attributes(clientRegistrationId("id")) for @HttpExchange @GetExchange, but not sure.
Question: how to combine Http Interface with spring-boot-starter-oauth2-client token?
You should setClientRegistrationIdResolver
on your OAuth2ClientHttpRequestInterceptor
instance:
final var interceptor = new OAuth2ClientHttpRequestInterceptor(authorizedClientManager);
// Whatever the request, always resolve the my-client registration defined in application.yml
interceptor.setClientRegistrationIdResolver((HttpRequest request) -> "my-client");
This is what I do in a starter of mine which helps auto-configuring RestClient
and WebClient
beans with various request authorization mechanisms (Basic
, Bearer
, and API key) and HTTP proxy, using just application properties. In your case that would give:
spring:
application:
name: client-application
security:
oauth2:
client:
registration:
my-registration:
provider: my-provider
client-id: ididid
client-secret: secretsecret
authorization-grant-type: client_credentials
scope: download
provider:
my-provider:
token-uri: https://provider.com/token
com:
c4-soft:
springaddons:
rest:
client:
user-client:
base-url: https://host.com
authorization:
oauth2:
oauth2-registration-id: my-registration
@Configuration
public class RestConfiguration {
@Bean
// userClient is auto-configured with OAuth2 security by spring-addons-starter-rest
// using the com.c4-soft.springaddons.rest.client.user-client properties above (the bean name "userClient" is the camelCase transformation of the "user-client" key in application properties)
UserApi userApi(RestClient userClient) throws Exception {
return new RestClientHttpExchangeProxyFactoryBean<>(UserApi.class, userClient).getObject();
}
}
@RestController
@RequiredArgsConstructor
public class LessonsController {
// the @HttpExchange proxy defined above (or auto-configured RestClient beans)
// can then be auto-wired in Spring components
private final UserApi userApi;
}
Bootiful isn't it?
Note that for clarification purposes, I renamed:
my-client
to my-registration
@HttpExchange
from UserClient
to UserApi
(userClient
being used as name for the auto-configured RestClient
@Bean
internally used by the the @HttpExchange
proxy)