step 1
This commit is contained in:
committed by
Olga Maciaszek-Sharma
parent
4dc2852ee0
commit
66cc168a33
@@ -215,6 +215,10 @@
|
||||
<version>2.11.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-client</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<profiles>
|
||||
<profile>
|
||||
|
||||
@@ -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<String> 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<OAuth2FeignRequestInterceptorConfigurer> configurers) {
|
||||
return buildWithConfigurers(oAuth2ClientContext, resource, configurers);
|
||||
return new OAuth2AccessTokenInterceptor(specifiedClientIds, oAuth2ClientProperties,
|
||||
oAuth2AuthorizedClientService, clientRegistrationRepository);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String> 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<String> specifiedClientIds, OAuth2ClientProperties oAuth2ClientProperties,
|
||||
OAuth2AuthorizedClientService oAuth2AuthorizedClientService,
|
||||
ClientRegistrationRepository clientRegistrationRepository) {
|
||||
this(BEARER, AUTHORIZATION, specifiedClientIds, oAuth2ClientProperties, oAuth2AuthorizedClientService,
|
||||
clientRegistrationRepository);
|
||||
}
|
||||
|
||||
public OAuth2AccessTokenInterceptor(String tokenType, String header, List<String> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<String>() {
|
||||
{
|
||||
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<String> 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"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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<String, OAuth2ClientProperties.Registration>() {
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -19,6 +19,7 @@
|
||||
<feign-form.version>3.8.0</feign-form.version>
|
||||
<!-- Deprecated - reached EOL -->
|
||||
<spring-security-oauth2-autoconfigure.version>2.5.2</spring-security-oauth2-autoconfigure.version>
|
||||
<spring-security-oauth2-client.version>6.0.0-SNAPSHOT</spring-security-oauth2-client.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
@@ -27,6 +28,11 @@
|
||||
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
|
||||
<version>${spring-security-oauth2-autoconfigure.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-oauth2-client</artifactId>
|
||||
<version>${spring-security-oauth2-client.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-openfeign-core</artifactId>
|
||||
|
||||
Reference in New Issue
Block a user