Im Using Spring Boot 2.7.2 together with Spring Cloud 2021.0. I have multiple OAuth2 providers:
spring:
security:
oauth2:
client:
provider:
provider-one:
authorization-uri: https://provider-one/oauth/authorize
token-uri: https://provider-one/oauth/token
provider-two:
authorization-uri: https://provider-two/oauth/authorize
token-uri: https://provider-two/oauth/token
registration:
client-one:
provider: provider-one
client-id: client-one
authorization-grant-type: client_credentials
client-two:
provider: provider-two
client-id: client-two
authorization-grant-type: client_credentials
We can define the client registration id for spring cloud openfeign:
spring:
cloud:
openfeign:
oauth2:
enabled: true
client-registration-id: client-one
But what do I do if I need FeignClientA
to get a token from provider-one
and I need FeignClientB
to get a token from provider-two
? Is there a configurative way to
achieve that or do I need to set one interceptor for each feign client myself?
Application properties:
spring:
cloud:
openfeign:
client:
config:
feign-client-one:
url: https://api-one
feign-client-two:
url: https://api-two
oauth2:
enabled: false
security:
oauth2:
client:
provider:
provider-one:
authorization-uri: https://provider-one/oauth/authorize
token-uri: https://provider-one/oauth/token
provider-two:
authorization-uri: https://provider-two/oauth/authorize
token-uri: https://provider-two/oauth/token
registration:
registration-one:
provider: provider-one
client-id: client-one
authorization-grant-type: client_credentials
registration-two:
provider: provider-two
client-id: client-two
authorization-grant-type: client_credentials
Mind:
registration-id
and not client-id
And then Feign clients with a request interceptor in conf:
@FeignClient(name = "feign-client-one", configuration = FeignClientOne.FeignConfiguration.class)
public interface FeignClientOne {
static class FeignConfiguration {
@Bean
OAuth2AccessTokenInterceptor oauth2AccessTokenInterceptorOne(OAuth2AuthorizedClientManager authorizedClientManager) {
return new OAuth2AccessTokenInterceptor("registration-one", authorizedClientManager);
}
}
}
@FeignClient(name = "feign-client-two", configuration = FeignClientTwo.FeignConfiguration.class)
public interface FeignClientTwo {
static class FeignConfiguration {
@Bean
OAuth2AccessTokenInterceptor oauth2AccessTokenInterceptorTwo(OAuth2AuthorizedClientManager authorizedClientManager) {
return new OAuth2AccessTokenInterceptor("registration-two", authorizedClientManager);
}
}
}
@HttpExchange
ProxiesIt seems that spring-cloud-openfeign
is entering "maintenance" mode. The references I could find about that are an answer from @OlgaMaciaszek and this issue on the Github repo.
I explored a bit around the alternative recommended by Olga and liked it enough to create a starter for REST clients auto-configuration.
With the same registration
properties as above, we can declare RestClient
(or WebClient
) beans with the following:
com:
c4-soft:
springaddons:
rest:
client:
client-one:
base-url: http://localhost:7081
authorization:
oauth2:
oauth2-registration-id: registration-one
client-two:
base-url: http://localhost:7082
authorization:
oauth2:
oauth2-registration-id: registration-two
The default names for auto-configured beans are the camelCase transformation of their keys in properties (client-one
=> clientOne
and client-two
=> clientTwo
). This can be changed in properties.
This auto-configured beans can be injected and used in any Spring @Component
. It can also be used in configuration to build @HttpExchange
proxies as follows:
@Configuration
public class RestConfiguration {
@Bean
ApiOne apiOne(RestClient clientOne) throws Exception {
return new RestClientHttpExchangeProxyFactoryBean<>(ApiOne.class, clientOne).getObject();
}
@Bean
ApiTwo apiTwo(RestClient clientTwo) throws Exception {
return new RestClientHttpExchangeProxyFactoryBean<>(ApiTwo.class, clientTwo).getObject();
}
}
Where ApiOne
and ApiTwo
are @HttpExchange
interfaces (which are purely declarative, just as @FeignClient
). Ideally they are generated from an OpenAPI spec, itself generated from the sources of the API to consume, but these are different stories.
The code using the RestClient
to authorize requests and call the remote APIs is entirely generated!