How to declare different Oauth2 client registration id's for each feign client?

348 Views Asked by At

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?

1

There are 1 best solutions below

3
ch4mp On BEST ANSWER

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:

  • the registration IDs which I changed to make it clearer that the request interceptor instances below reference registration-id and not client-id
  • the disabled default OAuth2 support (which binds to a single 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);
        }
    }
}

Edit: Migrating from @FeignClient to @HttpExchange proxy with RestClient

It 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 add some experimental support for Spring @HttpExchange factories to this starter I maintain (but be aware that it works only with Boot 3.2.2):

@HttpExchange(accept = MediaType.APPLICATION_JSON_VALUE)
public interface ClientOne {

    @GetExchange(url = "/users/count")
    Long getTotalUsersCount(@PathVariable(name = "realm") String realm);

}
@HttpExchange(accept = MediaType.APPLICATION_JSON_VALUE)
public interface ClientTwo {

    @GetExchange(url = "/users/count")
    Long getTotalUsersCount(@PathVariable(name = "realm") String realm);

}
@Bean
ClientOne clientOne(SpringAddonsRestClientSupport restSupport) {
    return restSupport.restClientService("client-one", ClientOne.class);
}

@Bean
ClientTwo clientTwo(SpringAddonsRestClientSupport restSupport) {
    return restSupport.restClientService("client-two", ClientTwo.class);
}
spring:
  security:
    oauth2:
      client:
        provider:
          provider-one:
            issuer-uri: https://issuer-one
          provider-two:
            issuer-uri: https://issuer-two
        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
com:
  c4-soft:
    springaddons:
      oidc:
        client:
          rest:
            client-one:
              base-uri: http://localhost:7081
              auth2-registration-id: registration-one
            client-two:
              base-uri: http://localhost:7082
              auth2-registration-id: registration-two

If you don't want to use "my" starter, you can have a look at BearerTokenAuthenticationInterceptor and AuthorizedClientBearerProvider for the RequestInterceptor impl and then at SpringAddonsRestClientSupport for the RestClient configuration and @Service proxying.