diff --git a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc index 625d69b5..e756a7ad 100644 --- a/docs/src/main/asciidoc/spring-cloud-openfeign.adoc +++ b/docs/src/main/asciidoc/spring-cloud-openfeign.adoc @@ -810,10 +810,12 @@ OAuth2 support can be enabled by setting following flag: spring.cloud.openfeign.oauth2.enabled=true ---- When the flag is set to true, and the oauth2 client context resource details are present, a bean of class `OAuth2AccessTokenInterceptor` is created. Before each request, the interceptor resolves the required access token and includes it as a header. -`OAuth2AccessTokenInterceptor` uses the `AuthorizedClientServiceOAuth2AuthorizedClientManager` to get `OAuth2AuthorizedClient` that holds an `OAuth2AccessToken`. If the user has specified an OAuth2 `clientRegistrationId` using the `spring.cloud.openfeign.oauth2.clientRegistrationId` property, it will be used to retrieve the token. If the token is not retrieved or the `clientRegistrationId` has not been specified, the `serviceId` retrieved from the `url` host segment will be used. +`OAuth2AccessTokenInterceptor` uses the `OAuth2AuthorizedClientManager` to get `OAuth2AuthorizedClient` that holds an `OAuth2AccessToken`. If the user has specified an OAuth2 `clientRegistrationId` using the `spring.cloud.openfeign.oauth2.clientRegistrationId` property, it will be used to retrieve the token. If the token is not retrieved or the `clientRegistrationId` has not been specified, the `serviceId` retrieved from the `url` host segment will be used. TIP:: Using the `serviceId` as OAuth2 client registrationId is convenient for load-balanced Feign clients. For non-load-balanced ones, the property-based `clientRegistrationId` is a suitable approach. +TIP:: If you do not want to use the default setup for the `OAuth2AuthorizedClientManager`, you can just instantiate a bean of this type in your configuration. + === Transform the load-balanced HTTP request You can use the selected `ServiceInstance` to transform the load-balanced HTTP Request. diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java index 0ab2ae95..7d262a51 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java @@ -69,6 +69,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.data.domain.Page; import org.springframework.data.domain.Sort; +import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; @@ -346,12 +347,21 @@ public class FeignAutoConfiguration { @Bean @ConditionalOnBean({ OAuth2AuthorizedClientService.class, ClientRegistrationRepository.class }) + @ConditionalOnMissingBean + OAuth2AuthorizedClientManager feignOAuth2AuthorizedClientManager( + ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientService oAuth2AuthorizedClientService) { + return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, + oAuth2AuthorizedClientService); + + } + + @Bean + @ConditionalOnBean(OAuth2AuthorizedClientManager.class) public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor( @Value("${spring.cloud.openfeign.oauth2.clientRegistrationId:}") String clientRegistrationId, - OAuth2AuthorizedClientService oAuth2AuthorizedClientService, - ClientRegistrationRepository clientRegistrationRepository) { - return new OAuth2AccessTokenInterceptor(clientRegistrationId, oAuth2AuthorizedClientService, - clientRegistrationRepository); + OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) { + return new OAuth2AccessTokenInterceptor(clientRegistrationId, oAuth2AuthorizedClientManager); } } diff --git a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java index dacd8b30..c113e244 100644 --- a/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java +++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java @@ -31,8 +31,6 @@ import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2A import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.util.Assert; import org.springframework.util.StringUtils; @@ -41,12 +39,13 @@ import org.springframework.util.StringUtils; * A {@link RequestInterceptor} for OAuth2 Feign Requests. By default, it uses the * {@link AuthorizedClientServiceOAuth2AuthorizedClientManager } to get * {@link OAuth2AuthorizedClient } that holds an {@link OAuth2AccessToken }. If the user - * has specified an OAuth2 {@code clientId} using the - * {@code spring.cloud.openfeign.oauth2.clientId} property, it will be used to retrieve - * the token. If the token is not retrieved or the {@code clientId} has not been - * specified, the {@code serviceId} retrieved from the {@code url} host segment will be - * used. This approach is convenient for load-balanced Feign clients. For - * non-load-balanced ones, the property-based {@code clientId} is a suitable approach. + * has specified an OAuth2 {@code clientRegistrationId} using the + * {@code spring.cloud.openfeign.oauth2.clientRegistrationId} property, it will be used to + * retrieve the token. If the token is not retrieved or the {@code clientRegistrationId} + * has not been specified, the {@code serviceId} retrieved from the {@code url} host + * segment will be used. This approach is convenient for load-balanced Feign clients. For + * non-load-balanced ones, the property-based {@code clientRegistrationId} is a suitable + * approach. * * @author Dangzhicairang(小水牛) * @author Olga Maciaszek-Sharma @@ -70,34 +69,26 @@ public class OAuth2AccessTokenInterceptor implements RequestInterceptor { private final String clientRegistrationId; - private OAuth2AuthorizedClientManager authorizedClientManager; - - public void setAuthorizedClientManager(OAuth2AuthorizedClientManager authorizedClientManager) { - this.authorizedClientManager = authorizedClientManager; - } + private final OAuth2AuthorizedClientManager authorizedClientManager; private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); - public OAuth2AccessTokenInterceptor(OAuth2AuthorizedClientService oAuth2AuthorizedClientService, - ClientRegistrationRepository clientRegistrationRepository) { - this(null, oAuth2AuthorizedClientService, clientRegistrationRepository); + public OAuth2AccessTokenInterceptor(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) { + this(null, oAuth2AuthorizedClientManager); } public OAuth2AccessTokenInterceptor(String clientRegistrationId, - OAuth2AuthorizedClientService oAuth2AuthorizedClientService, - ClientRegistrationRepository clientRegistrationRepository) { - this(BEARER, AUTHORIZATION, clientRegistrationId, oAuth2AuthorizedClientService, clientRegistrationRepository); + OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) { + this(BEARER, AUTHORIZATION, clientRegistrationId, oAuth2AuthorizedClientManager); } public OAuth2AccessTokenInterceptor(String tokenType, String header, String clientRegistrationId, - OAuth2AuthorizedClientService oAuth2AuthorizedClientService, - ClientRegistrationRepository clientRegistrationRepository) { + OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) { this.tokenType = tokenType; this.header = header; this.clientRegistrationId = clientRegistrationId; - this.authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager( - clientRegistrationRepository, oAuth2AuthorizedClientService); + this.authorizedClientManager = oAuth2AuthorizedClientManager; } @Override @@ -144,9 +135,9 @@ public class OAuth2AccessTokenInterceptor implements RequestInterceptor { private static String getServiceId(RequestTemplate template) { Target feignTarget = template.feignTarget(); - Assert.notNull(feignTarget, "feignTarget may not be null"); + Assert.notNull(feignTarget, "FeignTarget may not be null."); String url = feignTarget.url(); - Assert.hasLength(url, "url may not be empty"); + Assert.hasLength(url, "Url may not be empty."); final URI originalUri = URI.create(url); return originalUri.getHost(); } diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithLoadBalancerInterceptorTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithLoadBalancerInterceptorTests.java deleted file mode 100644 index e69de29b..00000000 diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithoutLoadBalancerInterceptorTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithoutLoadBalancerInterceptorTests.java deleted file mode 100644 index e69de29b..00000000 diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java index acedec4e..686ebab5 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java @@ -27,9 +27,7 @@ import org.junit.jupiter.api.Test; import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; -import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.registration.ClientRegistration; -import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.OAuth2AccessToken; @@ -49,12 +47,6 @@ import static org.mockito.Mockito.when; */ class OAuth2AccessTokenInterceptorTests { - private final ClientRegistrationRepository mockClientRegistrationRepository = mock( - ClientRegistrationRepository.class); - - private final OAuth2AuthorizedClientService mockOAuth2AuthorizedClientService = mock( - OAuth2AuthorizedClientService.class); - private final OAuth2AuthorizedClientManager mockOAuth2AuthorizedClientManager = mock( OAuth2AuthorizedClientManager.class); @@ -74,10 +66,8 @@ class OAuth2AccessTokenInterceptorTests { @Test void shouldThrowExceptionWhenNoTokenAcquired() { - oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientService, - mockClientRegistrationRepository); + oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); when(mockOAuth2AuthorizedClientManager.authorize(any())).thenReturn(null); - oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager); assertThatExceptionOfType(IllegalStateException.class) .isThrownBy(() -> oAuth2AccessTokenInterceptor.apply(requestTemplate)) @@ -86,10 +76,10 @@ class OAuth2AccessTokenInterceptorTests { @Test void shouldAcquireValidToken() { - oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientService, - mockClientRegistrationRepository); - when(mockOAuth2AuthorizedClientManager.authorize(any())).thenReturn(validTokenOAuth2AuthorizedClient()); - oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager); + oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); + when(mockOAuth2AuthorizedClientManager.authorize( + argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId())))) + .thenReturn(validTokenOAuth2AuthorizedClient()); oAuth2AccessTokenInterceptor.apply(requestTemplate); @@ -98,12 +88,10 @@ class OAuth2AccessTokenInterceptorTests { @Test void shouldAcquireValidTokenFromServiceId() { - oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientService, - mockClientRegistrationRepository); when(mockOAuth2AuthorizedClientManager.authorize( argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId())))) .thenReturn(validTokenOAuth2AuthorizedClient()); - oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager); + oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); oAuth2AccessTokenInterceptor.apply(requestTemplate); @@ -111,13 +99,12 @@ class OAuth2AccessTokenInterceptorTests { } @Test - void shouldAcquireValidTokenFromSpecifiedClientId() { + void shouldAcquireValidTokenFromSpecifiedClientRegistrationId() { oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(DEFAULT_CLIENT_REGISTRATION_ID, - mockOAuth2AuthorizedClientService, mockClientRegistrationRepository); + mockOAuth2AuthorizedClientManager); when(mockOAuth2AuthorizedClientManager .authorize(argThat((OAuth2AuthorizeRequest request) -> (DEFAULT_CLIENT_REGISTRATION_ID) .equals(request.getClientRegistrationId())))).thenReturn(validTokenOAuth2AuthorizedClient()); - oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager); oAuth2AccessTokenInterceptor.apply(requestTemplate);