Browse Source

Inject OAuth2AuthorizedClientManager via constructor.

pull/759/head
Olga Maciaszek-Sharma 2 years ago
parent
commit
09939b6174
  1. 4
      docs/src/main/asciidoc/spring-cloud-openfeign.adoc
  2. 18
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/FeignAutoConfiguration.java
  3. 41
      spring-cloud-openfeign-core/src/main/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptor.java
  4. 0
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithLoadBalancerInterceptorTests.java
  5. 0
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithoutLoadBalancerInterceptorTests.java
  6. 29
      spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/OAuth2AccessTokenInterceptorTests.java

4
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 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. 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:: 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 === Transform the load-balanced HTTP request
You can use the selected `ServiceInstance` to transform the load-balanced HTTP Request. You can use the selected `ServiceInstance` to transform the load-balanced HTTP Request.

18
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.context.annotation.Import;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort; 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.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
@ -346,12 +347,21 @@ public class FeignAutoConfiguration {
@Bean @Bean
@ConditionalOnBean({ OAuth2AuthorizedClientService.class, ClientRegistrationRepository.class }) @ConditionalOnBean({ OAuth2AuthorizedClientService.class, ClientRegistrationRepository.class })
@ConditionalOnMissingBean
OAuth2AuthorizedClientManager feignOAuth2AuthorizedClientManager(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService) {
return new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository,
oAuth2AuthorizedClientService);
}
@Bean
@ConditionalOnBean(OAuth2AuthorizedClientManager.class)
public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor( public OAuth2AccessTokenInterceptor defaultOAuth2AccessTokenInterceptor(
@Value("${spring.cloud.openfeign.oauth2.clientRegistrationId:}") String clientRegistrationId, @Value("${spring.cloud.openfeign.oauth2.clientRegistrationId:}") String clientRegistrationId,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService, OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
ClientRegistrationRepository clientRegistrationRepository) { return new OAuth2AccessTokenInterceptor(clientRegistrationId, oAuth2AuthorizedClientManager);
return new OAuth2AccessTokenInterceptor(clientRegistrationId, oAuth2AuthorizedClientService,
clientRegistrationRepository);
} }
} }

41
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.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; 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.security.oauth2.core.OAuth2AccessToken;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.StringUtils; 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 * A {@link RequestInterceptor} for OAuth2 Feign Requests. By default, it uses the
* {@link AuthorizedClientServiceOAuth2AuthorizedClientManager } to get * {@link AuthorizedClientServiceOAuth2AuthorizedClientManager } to get
* {@link OAuth2AuthorizedClient } that holds an {@link OAuth2AccessToken }. If the user * {@link OAuth2AuthorizedClient } that holds an {@link OAuth2AccessToken }. If the user
* has specified an OAuth2 {@code clientId} using the * has specified an OAuth2 {@code clientRegistrationId} using the
* {@code spring.cloud.openfeign.oauth2.clientId} property, it will be used to retrieve * {@code spring.cloud.openfeign.oauth2.clientRegistrationId} property, it will be used to
* the token. If the token is not retrieved or the {@code clientId} has not been * retrieve the token. If the token is not retrieved or the {@code clientRegistrationId}
* specified, the {@code serviceId} retrieved from the {@code url} host segment will be * has not been specified, the {@code serviceId} retrieved from the {@code url} host
* used. This approach is convenient for load-balanced Feign clients. For * 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. * non-load-balanced ones, the property-based {@code clientRegistrationId} is a suitable
* approach.
* *
* @author Dangzhicairang(小水牛) * @author Dangzhicairang(小水牛)
* @author Olga Maciaszek-Sharma * @author Olga Maciaszek-Sharma
@ -70,34 +69,26 @@ public class OAuth2AccessTokenInterceptor implements RequestInterceptor {
private final String clientRegistrationId; private final String clientRegistrationId;
private OAuth2AuthorizedClientManager authorizedClientManager; private final OAuth2AuthorizedClientManager authorizedClientManager;
public void setAuthorizedClientManager(OAuth2AuthorizedClientManager authorizedClientManager) {
this.authorizedClientManager = authorizedClientManager;
}
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous",
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
public OAuth2AccessTokenInterceptor(OAuth2AuthorizedClientService oAuth2AuthorizedClientService, public OAuth2AccessTokenInterceptor(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
ClientRegistrationRepository clientRegistrationRepository) { this(null, oAuth2AuthorizedClientManager);
this(null, oAuth2AuthorizedClientService, clientRegistrationRepository);
} }
public OAuth2AccessTokenInterceptor(String clientRegistrationId, public OAuth2AccessTokenInterceptor(String clientRegistrationId,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService, OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
ClientRegistrationRepository clientRegistrationRepository) { this(BEARER, AUTHORIZATION, clientRegistrationId, oAuth2AuthorizedClientManager);
this(BEARER, AUTHORIZATION, clientRegistrationId, oAuth2AuthorizedClientService, clientRegistrationRepository);
} }
public OAuth2AccessTokenInterceptor(String tokenType, String header, String clientRegistrationId, public OAuth2AccessTokenInterceptor(String tokenType, String header, String clientRegistrationId,
OAuth2AuthorizedClientService oAuth2AuthorizedClientService, OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) {
ClientRegistrationRepository clientRegistrationRepository) {
this.tokenType = tokenType; this.tokenType = tokenType;
this.header = header; this.header = header;
this.clientRegistrationId = clientRegistrationId; this.clientRegistrationId = clientRegistrationId;
this.authorizedClientManager = new AuthorizedClientServiceOAuth2AuthorizedClientManager( this.authorizedClientManager = oAuth2AuthorizedClientManager;
clientRegistrationRepository, oAuth2AuthorizedClientService);
} }
@Override @Override
@ -144,9 +135,9 @@ public class OAuth2AccessTokenInterceptor implements RequestInterceptor {
private static String getServiceId(RequestTemplate template) { private static String getServiceId(RequestTemplate template) {
Target<?> feignTarget = template.feignTarget(); Target<?> feignTarget = template.feignTarget();
Assert.notNull(feignTarget, "feignTarget may not be null"); Assert.notNull(feignTarget, "FeignTarget may not be null.");
String url = feignTarget.url(); 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); final URI originalUri = URI.create(url);
return originalUri.getHost(); return originalUri.getHost();
} }

0
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithLoadBalancerInterceptorTests.java

0
spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/security/AccessTokenProviderWithoutLoadBalancerInterceptorTests.java

29
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.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; 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.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType; import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken; import org.springframework.security.oauth2.core.OAuth2AccessToken;
@ -49,12 +47,6 @@ import static org.mockito.Mockito.when;
*/ */
class OAuth2AccessTokenInterceptorTests { class OAuth2AccessTokenInterceptorTests {
private final ClientRegistrationRepository mockClientRegistrationRepository = mock(
ClientRegistrationRepository.class);
private final OAuth2AuthorizedClientService mockOAuth2AuthorizedClientService = mock(
OAuth2AuthorizedClientService.class);
private final OAuth2AuthorizedClientManager mockOAuth2AuthorizedClientManager = mock( private final OAuth2AuthorizedClientManager mockOAuth2AuthorizedClientManager = mock(
OAuth2AuthorizedClientManager.class); OAuth2AuthorizedClientManager.class);
@ -74,10 +66,8 @@ class OAuth2AccessTokenInterceptorTests {
@Test @Test
void shouldThrowExceptionWhenNoTokenAcquired() { void shouldThrowExceptionWhenNoTokenAcquired() {
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientService, oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager);
mockClientRegistrationRepository);
when(mockOAuth2AuthorizedClientManager.authorize(any())).thenReturn(null); when(mockOAuth2AuthorizedClientManager.authorize(any())).thenReturn(null);
oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager);
assertThatExceptionOfType(IllegalStateException.class) assertThatExceptionOfType(IllegalStateException.class)
.isThrownBy(() -> oAuth2AccessTokenInterceptor.apply(requestTemplate)) .isThrownBy(() -> oAuth2AccessTokenInterceptor.apply(requestTemplate))
@ -86,10 +76,10 @@ class OAuth2AccessTokenInterceptorTests {
@Test @Test
void shouldAcquireValidToken() { void shouldAcquireValidToken() {
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientService, oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager);
mockClientRegistrationRepository); when(mockOAuth2AuthorizedClientManager.authorize(
when(mockOAuth2AuthorizedClientManager.authorize(any())).thenReturn(validTokenOAuth2AuthorizedClient()); argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId()))))
oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager); .thenReturn(validTokenOAuth2AuthorizedClient());
oAuth2AccessTokenInterceptor.apply(requestTemplate); oAuth2AccessTokenInterceptor.apply(requestTemplate);
@ -98,12 +88,10 @@ class OAuth2AccessTokenInterceptorTests {
@Test @Test
void shouldAcquireValidTokenFromServiceId() { void shouldAcquireValidTokenFromServiceId() {
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientService,
mockClientRegistrationRepository);
when(mockOAuth2AuthorizedClientManager.authorize( when(mockOAuth2AuthorizedClientManager.authorize(
argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId())))) argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId()))))
.thenReturn(validTokenOAuth2AuthorizedClient()); .thenReturn(validTokenOAuth2AuthorizedClient());
oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager); oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager);
oAuth2AccessTokenInterceptor.apply(requestTemplate); oAuth2AccessTokenInterceptor.apply(requestTemplate);
@ -111,13 +99,12 @@ class OAuth2AccessTokenInterceptorTests {
} }
@Test @Test
void shouldAcquireValidTokenFromSpecifiedClientId() { void shouldAcquireValidTokenFromSpecifiedClientRegistrationId() {
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(DEFAULT_CLIENT_REGISTRATION_ID, oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(DEFAULT_CLIENT_REGISTRATION_ID,
mockOAuth2AuthorizedClientService, mockClientRegistrationRepository); mockOAuth2AuthorizedClientManager);
when(mockOAuth2AuthorizedClientManager when(mockOAuth2AuthorizedClientManager
.authorize(argThat((OAuth2AuthorizeRequest request) -> (DEFAULT_CLIENT_REGISTRATION_ID) .authorize(argThat((OAuth2AuthorizeRequest request) -> (DEFAULT_CLIENT_REGISTRATION_ID)
.equals(request.getClientRegistrationId())))).thenReturn(validTokenOAuth2AuthorizedClient()); .equals(request.getClientRegistrationId())))).thenReturn(validTokenOAuth2AuthorizedClient());
oAuth2AccessTokenInterceptor.setAuthorizedClientManager(mockOAuth2AuthorizedClientManager);
oAuth2AccessTokenInterceptor.apply(requestTemplate); oAuth2AccessTokenInterceptor.apply(requestTemplate);

Loading…
Cancel
Save