diff --git a/spring-cloud-openfeign-core/pom.xml b/spring-cloud-openfeign-core/pom.xml
index 6ac76c20..9045b45c 100644
--- a/spring-cloud-openfeign-core/pom.xml
+++ b/spring-cloud-openfeign-core/pom.xml
@@ -215,6 +215,10 @@
2.11.0
test
+
+ org.springframework.security
+ spring-security-oauth2-client
+
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 c40bfb66..889aa3ce 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
@@ -29,7 +29,6 @@ import com.fasterxml.jackson.databind.Module;
import feign.Capability;
import feign.Client;
import feign.Feign;
-import feign.RequestInterceptor;
import feign.Target;
import feign.hc5.ApacheHttp5Client;
import feign.httpclient.ApacheHttpClient;
@@ -50,19 +49,17 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.interceptor.CacheInterceptor;
import org.springframework.cloud.client.actuator.HasFeatures;
import org.springframework.cloud.client.circuitbreaker.CircuitBreaker;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
-import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientConnectionManagerFactory;
import org.springframework.cloud.commons.httpclient.ApacheHttpClientFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientConnectionPoolFactory;
import org.springframework.cloud.commons.httpclient.OkHttpClientFactory;
-import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
-import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptorConfigurer;
+import org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor;
import org.springframework.cloud.openfeign.support.FeignEncoderProperties;
import org.springframework.cloud.openfeign.support.FeignHttpClientProperties;
import org.springframework.cloud.openfeign.support.PageJacksonModule;
@@ -73,10 +70,8 @@ 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.OAuth2ClientContext;
-import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
-
-import static org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptorBuilder.buildWithConfigurers;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
/**
* @author Spencer Gibb
@@ -90,6 +85,7 @@ import static org.springframework.cloud.openfeign.security.OAuth2FeignRequestInt
* @author Kwangyong Kim
* @author Sam Kruglov
* @author Wojciech Mąka
+ * @author Dangzhicairang(小水牛)
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(Feign.class)
@@ -344,35 +340,20 @@ public class FeignAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
- @ConditionalOnClass(OAuth2ClientContext.class)
+ @EnableConfigurationProperties(OAuth2ClientProperties.class)
@ConditionalOnProperty("spring.cloud.openfeign.oauth2.enabled")
- @Deprecated // spring-security-oauth2 reached EOL
protected static class Oauth2FeignConfiguration {
- @ConditionalOnBean({ RetryLoadBalancerInterceptor.class, OAuth2ClientContext.class,
- OAuth2ProtectedResourceDetails.class })
- @ConditionalOnProperty(value = "spring.cloud.openfeign.oauth2.load-balanced", havingValue = "true")
@Bean
- public OAuth2FeignRequestInterceptorConfigurer retryLoadBalancerInterceptorInjectingConfigurer(
- final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
- return builder -> builder.withAccessTokenProviderInterceptors(loadBalancerInterceptor);
- }
+ @ConditionalOnBean({ OAuth2AuthorizedClientService.class, ClientRegistrationRepository.class })
+ public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor(
+ @Value("${spring.cloud.openfeign.oauth2.specifiedClientIds:}") List specifiedClientIds,
+ OAuth2ClientProperties oAuth2ClientProperties,
+ OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
+ ClientRegistrationRepository clientRegistrationRepository) {
- @ConditionalOnBean({ LoadBalancerInterceptor.class, OAuth2ClientContext.class,
- OAuth2ProtectedResourceDetails.class })
- @ConditionalOnProperty(value = "spring.cloud.openfeign.oauth2.load-balanced", havingValue = "true")
- @Bean
- public OAuth2FeignRequestInterceptorConfigurer loadBalancerInterceptorInjectingConfigurer(
- final LoadBalancerInterceptor loadBalancerInterceptor) {
- return builder -> builder.withAccessTokenProviderInterceptors(loadBalancerInterceptor);
- }
-
- @Bean
- @ConditionalOnMissingBean(OAuth2FeignRequestInterceptor.class)
- @ConditionalOnBean({ OAuth2ClientContext.class, OAuth2ProtectedResourceDetails.class })
- public RequestInterceptor oauth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext,
- OAuth2ProtectedResourceDetails resource, List configurers) {
- return buildWithConfigurers(oAuth2ClientContext, resource, configurers);
+ return new OAuth2AccessTokenInterceptor(specifiedClientIds, oAuth2ClientProperties,
+ oAuth2AuthorizedClientService, clientRegistrationRepository);
}
}
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
new file mode 100644
index 00000000..d695d871
--- /dev/null
+++ b/spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2015-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.openfeign.security;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import feign.RequestInterceptor;
+import feign.RequestTemplate;
+
+import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
+import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
+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.StringUtils;
+
+/**
+ * RequestInterceptor for OAuth2 Feign Requests. By default, It uses the
+ * {@link AuthorizedClientServiceOAuth2AuthorizedClientManager } to get
+ * {@link OAuth2AuthorizedClient } that hold an {@link OAuth2AccessToken }. Use the
+ * Client(s) from properties if not specific the field
+ * {@link OAuth2AccessTokenInterceptor#specifiedClientIds}
+ *
+ * @author Dangzhicairang(小水牛)
+ * @since 4.0.0
+ */
+public class OAuth2AccessTokenInterceptor implements RequestInterceptor {
+
+ /**
+ * The name of the token.
+ */
+ public static final String BEARER = "Bearer";
+
+ /**
+ * The name of the header.
+ */
+ public static final String AUTHORIZATION = "Authorization";
+
+ private final String tokenType;
+
+ private final String header;
+
+ private final List specifiedClientIds;
+
+ private final OAuth2ClientProperties oAuth2ClientProperties;
+
+ private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService;
+
+ private OAuth2AuthorizedClientManager authorizedClientManager;
+
+ public void setAuthorizedClientManager(OAuth2AuthorizedClientManager authorizedClientManager) {
+ this.authorizedClientManager = authorizedClientManager;
+ }
+
+ private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
+ "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
+
+ public OAuth2AccessTokenInterceptor(OAuth2ClientProperties oAuth2ClientProperties,
+ OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
+ ClientRegistrationRepository clientRegistrationRepository) {
+ this(new ArrayList<>(), oAuth2ClientProperties, oAuth2AuthorizedClientService, clientRegistrationRepository);
+ }
+
+ public OAuth2AccessTokenInterceptor(List specifiedClientIds, OAuth2ClientProperties oAuth2ClientProperties,
+ OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
+ ClientRegistrationRepository clientRegistrationRepository) {
+ this(BEARER, AUTHORIZATION, specifiedClientIds, oAuth2ClientProperties, oAuth2AuthorizedClientService,
+ clientRegistrationRepository);
+ }
+
+ public OAuth2AccessTokenInterceptor(String tokenType, String header, List specifiedClientIds,
+ OAuth2ClientProperties oAuth2ClientProperties, OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
+ ClientRegistrationRepository clientRegistrationRepository) {
+ this.tokenType = tokenType;
+ this.header = header;
+ this.specifiedClientIds = specifiedClientIds;
+ this.oAuth2ClientProperties = oAuth2ClientProperties;
+ this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService;
+ this.authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager(
+ clientRegistrationRepository, this.oAuth2AuthorizedClientService);
+ }
+
+ @Override
+ public void apply(RequestTemplate template) {
+ template.header(header);
+ template.header(header, extract(tokenType));
+ }
+
+ protected String extract(String tokenType) {
+ OAuth2AccessToken accessToken = getToken();
+ return String.format("%s %s", tokenType, accessToken.getTokenValue());
+ }
+
+ public OAuth2AccessToken getToken() {
+
+ // if specific, try to use them to get token.
+ for (String clientId : this.specifiedClientIds) {
+ OAuth2AccessToken token = this.getToken(clientId);
+ if (token != null) {
+ return token;
+ }
+ }
+
+ // use clients from properties by default
+ for (String clientId : Optional.ofNullable(this.oAuth2ClientProperties)
+ .map(OAuth2ClientProperties::getRegistration).map(Map::keySet).orElse(new HashSet<>())) {
+ OAuth2AccessToken token = this.getToken(clientId);
+ if (token != null) {
+ return token;
+ }
+ }
+
+ throw new IllegalStateException("No token acquired, which is illegal according to the contract.");
+ }
+
+ protected OAuth2AccessToken getToken(String clientId) {
+
+ if (!StringUtils.hasText(clientId)) {
+ return null;
+ }
+
+ Authentication principal = SecurityContextHolder.getContext().getAuthentication();
+ if (principal == null) {
+ principal = ANONYMOUS_AUTHENTICATION;
+ }
+
+ // already exist
+ OAuth2AuthorizedClient oAuth2AuthorizedClient = oAuth2AuthorizedClientService.loadAuthorizedClient(clientId,
+ principal.getName());
+ if (oAuth2AuthorizedClient != null) {
+ OAuth2AccessToken accessToken = oAuth2AuthorizedClient.getAccessToken();
+ if (accessToken != null && this.noExpire(accessToken)) {
+ return accessToken;
+ }
+ }
+
+ OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientId)
+ .principal(principal).build();
+ OAuth2AuthorizedClient authorize = this.authorizedClientManager.authorize(authorizeRequest);
+ return Optional.ofNullable(authorize).map(OAuth2AuthorizedClient::getAccessToken).filter(this::noExpire)
+ .orElse(null);
+ }
+
+ protected boolean noExpire(OAuth2AccessToken token) {
+ return Optional.ofNullable(token).map(OAuth2AccessToken::getExpiresAt)
+ .map(expire -> expire.isAfter(Instant.now())).orElse(false);
+ }
+
+}
diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java
index bcf67e35..222fa3f3 100644
--- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java
+++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/FeignAutoConfigurationTests.java
@@ -17,6 +17,8 @@
package org.springframework.cloud.openfeign;
import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
import feign.Target;
import org.assertj.core.api.Condition;
@@ -26,16 +28,11 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.assertj.AssertableApplicationContext;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cloud.client.circuitbreaker.CircuitBreakerFactory;
-import org.springframework.cloud.client.loadbalancer.LoadBalancerInterceptor;
import org.springframework.cloud.openfeign.FeignAutoConfiguration.CircuitBreakerPresentFeignTargeterConfiguration.AlphanumericCircuitBreakerNameResolver;
-import org.springframework.cloud.openfeign.security.MockOAuth2ClientContext;
-import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptor;
-import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptorBuilder;
-import org.springframework.cloud.openfeign.security.OAuth2FeignRequestInterceptorConfigurer;
+import org.springframework.cloud.openfeign.security.OAuth2AccessTokenInterceptor;
import org.springframework.context.ConfigurableApplicationContext;
-import org.springframework.http.client.ClientHttpRequestInterceptor;
-import org.springframework.http.client.support.BasicAuthenticationInterceptor;
-import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails;
+import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
+import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -46,6 +43,7 @@ import static org.mockito.Mockito.mock;
* @author Andrii Bohutskyi
* @author Kwangyong Kim
* @author Wojciech Mąka
+ * @author Dangzhicairang(小水牛)
*/
class FeignAutoConfigurationTests {
@@ -93,63 +91,30 @@ class FeignAutoConfigurationTests {
@Test
void shouldInstantiateFeignOAuth2FeignRequestInterceptorWithoutInterceptors() {
- runner.withPropertyValues("spring.cloud.openfeign.oauth2.enabled=true")
- .withBean(MockOAuth2ClientContext.class, "token").withBean(BaseOAuth2ProtectedResourceDetails.class)
- .withBean(LoadBalancerInterceptor.class, () -> mock(LoadBalancerInterceptor.class)).run(ctx -> {
- assertOauth2FeignRequestInterceptorExists(ctx);
- assertAccessTokenProviderInterceptorNotExists(ctx, LoadBalancerInterceptor.class);
- });
- }
-
- @Test
- void shouldInstantiateFeignOAuth2FeignRequestInterceptorWithLoadBalancedInterceptor() {
runner.withPropertyValues("spring.cloud.openfeign.oauth2.enabled=true",
- "spring.cloud.openfeign.oauth2.load-balanced=true").withBean(MockOAuth2ClientContext.class, "token")
- .withBean(BaseOAuth2ProtectedResourceDetails.class)
- .withBean(LoadBalancerInterceptor.class, () -> mock(LoadBalancerInterceptor.class)).run(ctx -> {
- assertOauth2FeignRequestInterceptorExists(ctx);
- assertAccessTokenProviderInterceptorExists(ctx, LoadBalancerInterceptor.class);
+ "spring.cloud.openfeign.oauth2.specifiedClientIds=feign-client")
+ .withBean(OAuth2AuthorizedClientService.class, () -> mock(OAuth2AuthorizedClientService.class))
+ .withBean(ClientRegistrationRepository.class, () -> mock(ClientRegistrationRepository.class))
+ .run(ctx -> {
+ assertOauth2AccessTokenInterceptorExists(ctx);
+ assertThatOauth2AccessTokenInterceptorHasSpecifiedIdsPropertyWithValue(ctx,
+ new ArrayList() {
+ {
+ add("feign-client");
+ }
+ });
});
}
- @Test
- void shouldInstantiateFeignOAuth2FeignRequestInterceptorWithoutLoadBalancedInterceptorIfNoBeanPresent() {
- runner.withPropertyValues("spring.cloud.openfeign.oauth2.enabled=true",
- "spring.cloud.openfeign.oauth2.load-balanced=true").withBean(MockOAuth2ClientContext.class, "token")
- .withBean(BaseOAuth2ProtectedResourceDetails.class).run(ctx -> {
- assertOauth2FeignRequestInterceptorExists(ctx);
- assertAccessTokenProviderInterceptorNotExists(ctx, LoadBalancerInterceptor.class);
- });
- }
-
- @Test
- void shouldInstantiateFeignOAuth2FeignRequestInterceptorWithCustomAccessTokenProviderInterceptor() {
- runner.withPropertyValues("spring.cloud.openfeign.oauth2.enabled=true")
- .withBean(MockOAuth2ClientContext.class, "token").withBean(BaseOAuth2ProtectedResourceDetails.class)
- .withBean(CustomOAuth2FeignRequestInterceptorConfigurer.class).run(ctx -> {
- assertOauth2FeignRequestInterceptorExists(ctx);
- assertAccessTokenProviderInterceptorExists(ctx, BasicAuthenticationInterceptor.class);
- });
- }
-
- private void assertOauth2FeignRequestInterceptorExists(ConfigurableApplicationContext ctx) {
+ private void assertOauth2AccessTokenInterceptorExists(ConfigurableApplicationContext ctx) {
AssertableApplicationContext context = AssertableApplicationContext.get(() -> ctx);
- assertThat(context).hasSingleBean(OAuth2FeignRequestInterceptor.class);
+ assertThat(context).hasSingleBean(OAuth2AccessTokenInterceptor.class);
}
- private void assertAccessTokenProviderInterceptorExists(ConfigurableApplicationContext ctx,
- Class extends ClientHttpRequestInterceptor> clazz) {
- AssertableApplicationContext context = AssertableApplicationContext.get(() -> ctx);
- assertThat(context).getBean(OAuth2FeignRequestInterceptor.class).extracting("accessTokenProvider")
- .extracting("interceptors").asList().first().isInstanceOf(clazz);
- }
-
- private void assertAccessTokenProviderInterceptorNotExists(ConfigurableApplicationContext ctx,
- Class extends ClientHttpRequestInterceptor> clazz) {
- AssertableApplicationContext context = AssertableApplicationContext.get(() -> ctx);
- assertThat(context).getBean(OAuth2FeignRequestInterceptor.class).extracting("accessTokenProvider")
- .extracting("interceptors").asList().filteredOn(obj -> clazz.isAssignableFrom(obj.getClass()))
- .isEmpty();
+ private void assertThatOauth2AccessTokenInterceptorHasSpecifiedIdsPropertyWithValue(
+ ConfigurableApplicationContext ctx, List expectedValue) {
+ final OAuth2AccessTokenInterceptor bean = ctx.getBean(OAuth2AccessTokenInterceptor.class);
+ assertThat(bean).hasFieldOrPropertyWithValue("specifiedClientIds", expectedValue);
}
private void assertOnlyOneTargeterPresent(ConfigurableApplicationContext ctx, Class> beanClass) {
@@ -179,14 +144,4 @@ class FeignAutoConfigurationTests {
}
- static class CustomOAuth2FeignRequestInterceptorConfigurer implements OAuth2FeignRequestInterceptorConfigurer {
-
- @Override
- public void customize(OAuth2FeignRequestInterceptorBuilder requestInterceptorBuilder) {
- requestInterceptorBuilder
- .withAccessTokenProviderInterceptors(new BasicAuthenticationInterceptor("username", "password"));
- }
-
- }
-
}
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
index cec77e98..767ab515 100644
--- 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
@@ -41,11 +41,11 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Wojciech Mąka
*/
@SpringBootTest(classes = AccessTokenProviderWithLoadBalancerInterceptorTests.Application.class,
- webEnvironment = RANDOM_PORT,
- value = { "security.oauth2.client.id=test-service", "security.oauth2.client.client-id=test-service",
- "security.oauth2.client.client-secret=test-service",
- "security.oauth2.client.grant-type=client_credentials", "spring.cloud.openfeign.oauth2.enabled=true",
- "spring.cloud.openfeign.oauth2.load-balanced=true" })
+ webEnvironment = RANDOM_PORT,
+ value = { "security.oauth2.client.id=test-service", "security.oauth2.client.client-id=test-service",
+ "security.oauth2.client.client-secret=test-service",
+ "security.oauth2.client.grant-type=client_credentials", "spring.cloud.openfeign.oauth2.enabled=true",
+ "spring.cloud.openfeign.oauth2.load-balanced=true" })
@DirtiesContext
public class AccessTokenProviderWithLoadBalancerInterceptorTests {
@@ -70,7 +70,7 @@ public class AccessTokenProviderWithLoadBalancerInterceptorTests {
@EnableAutoConfiguration
@RestController
@EnableFeignClients(
- clients = { AccessTokenProviderWithLoadBalancerInterceptorTests.Application.SampleClient.class })
+ clients = { AccessTokenProviderWithLoadBalancerInterceptorTests.Application.SampleClient.class })
protected static class Application {
@GetMapping("/foo")
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
index 4693a137..d6c55f82 100644
--- 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
@@ -41,10 +41,10 @@ import static org.springframework.boot.test.context.SpringBootTest.WebEnvironmen
* @author Wojciech Mąka
*/
@SpringBootTest(classes = AccessTokenProviderWithoutLoadBalancerInterceptorTests.Application.class,
- webEnvironment = RANDOM_PORT,
- value = { "security.oauth2.client.id=test-service", "security.oauth2.client.client-id=test-service",
- "security.oauth2.client.client-secret=test-service",
- "security.oauth2.client.grant-type=client_credentials", "spring.cloud.openfeign.oauth2.enabled=true" })
+ webEnvironment = RANDOM_PORT,
+ value = { "security.oauth2.client.id=test-service", "security.oauth2.client.client-id=test-service",
+ "security.oauth2.client.client-secret=test-service",
+ "security.oauth2.client.grant-type=client_credentials", "spring.cloud.openfeign.oauth2.enabled=true" })
@DirtiesContext
public class AccessTokenProviderWithoutLoadBalancerInterceptorTests {
@@ -70,7 +70,7 @@ public class AccessTokenProviderWithoutLoadBalancerInterceptorTests {
@EnableAutoConfiguration
@RestController
@EnableFeignClients(
- clients = { AccessTokenProviderWithoutLoadBalancerInterceptorTests.Application.SampleClient.class })
+ clients = { AccessTokenProviderWithoutLoadBalancerInterceptorTests.Application.SampleClient.class })
protected static class Application {
@GetMapping("/foo")
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
new file mode 100644
index 00000000..3066bd61
--- /dev/null
+++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2015-2022 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.cloud.openfeign.security;
+
+import java.time.Instant;
+import java.util.HashMap;
+
+import feign.Request.HttpMethod;
+import feign.RequestTemplate;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
+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;
+import org.springframework.util.AlternativeJdkIdGenerator;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+/**
+ * @author Dangzhicairang(小水牛)
+ */
+class OAuth2AccessTokenInterceptorTests {
+
+ private OAuth2AccessTokenInterceptor oAuth2AccessTokenInterceptor;
+
+ private RequestTemplate requestTemplate;
+
+ private OAuth2ClientProperties mockOAuth2ClientProperties;
+
+ private static final String DEFAULT_CLIENT_ID = "feign-client";
+
+ @BeforeEach
+ void setUp() {
+
+ requestTemplate = new RequestTemplate().method(HttpMethod.GET);
+
+ mockOAuth2ClientProperties = mock(OAuth2ClientProperties.class);
+ given(mockOAuth2ClientProperties.getRegistration())
+ .willReturn(new HashMap() {
+ {
+ put(DEFAULT_CLIENT_ID, mock(OAuth2ClientProperties.Registration.class));
+ }
+ });
+
+ }
+
+ @Test
+ void noTokenAcquired() {
+
+ OAuth2AuthorizedClientService mockOAuth2AuthorizedClientService = mock(OAuth2AuthorizedClientService.class);
+ given(mockOAuth2AuthorizedClientService.loadAuthorizedClient(anyString(), anyString())).willReturn(null);
+
+ oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2ClientProperties,
+ mockOAuth2AuthorizedClientService, mock(ClientRegistrationRepository.class));
+
+ OAuth2AuthorizedClientManager mockOAuth2AuthorizedClientManager = mock(OAuth2AuthorizedClientManager.class);
+ given(mockOAuth2AuthorizedClientManager.authorize(any())).willReturn(null);
+
+ oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager);
+
+ Assertions.assertThatExceptionOfType(IllegalStateException.class)
+ .isThrownBy(() -> oAuth2AccessTokenInterceptor.apply(requestTemplate))
+ .withMessage("No token acquired, which is illegal according to the contract.");
+
+ }
+
+ @Test
+ void validTokenAcquired() {
+
+ OAuth2AuthorizedClientService mockOAuth2AuthorizedClientService = mock(OAuth2AuthorizedClientService.class);
+ given(mockOAuth2AuthorizedClientService.loadAuthorizedClient(anyString(), anyString())).willReturn(null);
+
+ oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2ClientProperties,
+ mockOAuth2AuthorizedClientService, mock(ClientRegistrationRepository.class));
+
+ OAuth2AuthorizedClientManager mockOAuth2AuthorizedClientManager = mock(OAuth2AuthorizedClientManager.class);
+ given(mockOAuth2AuthorizedClientManager.authorize(any())).willReturn(validTokenOAuth2AuthorizedClient());
+
+ oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager);
+
+ oAuth2AccessTokenInterceptor.apply(requestTemplate);
+
+ Assertions.assertThat(requestTemplate.headers().get("Authorization")).contains("Bearer Valid Token");
+ }
+
+ @Test
+ void expireTokenAcquired() {
+
+ OAuth2AuthorizedClientService mockOAuth2AuthorizedClientService = mock(OAuth2AuthorizedClientService.class);
+ given(mockOAuth2AuthorizedClientService.loadAuthorizedClient(anyString(), anyString())).willReturn(null);
+
+ oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2ClientProperties,
+ mockOAuth2AuthorizedClientService, mock(ClientRegistrationRepository.class));
+
+ OAuth2AuthorizedClientManager mockOAuth2AuthorizedClientManager = mock(OAuth2AuthorizedClientManager.class);
+ given(mockOAuth2AuthorizedClientManager.authorize(any())).willReturn(expiredTokenOAuth2AuthorizedClient());
+
+ oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager);
+
+ Assertions.assertThatExceptionOfType(IllegalStateException.class)
+ .isThrownBy(() -> oAuth2AccessTokenInterceptor.apply(requestTemplate))
+ .withMessage("No token acquired, which is illegal according to the contract.");
+ }
+
+ @Test
+ void acquireTokenFromAuthorizedClient() {
+ OAuth2AuthorizedClientService mockOAuth2AuthorizedClientService = mock(OAuth2AuthorizedClientService.class);
+ given(mockOAuth2AuthorizedClientService.loadAuthorizedClient(anyString(), anyString()))
+ .willReturn(validTokenOAuth2AuthorizedClient());
+
+ oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2ClientProperties,
+ mockOAuth2AuthorizedClientService, mock(ClientRegistrationRepository.class));
+
+ oAuth2AccessTokenInterceptor.apply(requestTemplate);
+
+ Assertions.assertThat(requestTemplate.headers().get("Authorization")).contains("Bearer Valid Token");
+ }
+
+ private OAuth2AccessToken validToken() {
+ return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "Valid Token", Instant.now(),
+ Instant.now().plusSeconds(60L));
+ }
+
+ private OAuth2AccessToken expiredToken() {
+ return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "Expired Token",
+ Instant.now().minusSeconds(61L), Instant.now().minusSeconds(60L));
+ }
+
+ private OAuth2AuthorizedClient validTokenOAuth2AuthorizedClient() {
+ return new OAuth2AuthorizedClient(defaultClientRegistration(), "anonymousUser", validToken());
+ }
+
+ private OAuth2AuthorizedClient expiredTokenOAuth2AuthorizedClient() {
+ return new OAuth2AuthorizedClient(defaultClientRegistration(), "anonymousUser", expiredToken());
+ }
+
+ private ClientRegistration defaultClientRegistration() {
+ return ClientRegistration.withRegistrationId(new AlternativeJdkIdGenerator().generateId().toString())
+ .clientId(DEFAULT_CLIENT_ID).tokenUri("mock token uri")
+ .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).build();
+ }
+
+}
diff --git a/spring-cloud-openfeign-dependencies/pom.xml b/spring-cloud-openfeign-dependencies/pom.xml
index 9c10c686..98cd1aa5 100644
--- a/spring-cloud-openfeign-dependencies/pom.xml
+++ b/spring-cloud-openfeign-dependencies/pom.xml
@@ -19,6 +19,7 @@
3.8.0
2.5.2
+ 6.0.0-SNAPSHOT
@@ -27,6 +28,11 @@
spring-security-oauth2-autoconfigure
${spring-security-oauth2-autoconfigure.version}
+
+ org.springframework.security
+ spring-security-oauth2-client
+ ${spring-security-oauth2-client.version}
+
org.springframework.cloud
spring-cloud-openfeign-core