17 changed files with 323 additions and 953 deletions
@ -0,0 +1,145 @@
@@ -0,0 +1,145 @@
|
||||
/* |
||||
* 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.net.URI; |
||||
import java.util.Optional; |
||||
|
||||
import feign.RequestInterceptor; |
||||
import feign.RequestTemplate; |
||||
import feign.Target; |
||||
|
||||
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.core.OAuth2AccessToken; |
||||
import org.springframework.util.Assert; |
||||
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 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 |
||||
* @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 String clientRegistrationId; |
||||
|
||||
private final OAuth2AuthorizedClientManager authorizedClientManager; |
||||
|
||||
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken("anonymous", |
||||
"anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); |
||||
|
||||
public OAuth2AccessTokenInterceptor(OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) { |
||||
this(null, oAuth2AuthorizedClientManager); |
||||
} |
||||
|
||||
public OAuth2AccessTokenInterceptor(String clientRegistrationId, |
||||
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) { |
||||
this(BEARER, AUTHORIZATION, clientRegistrationId, oAuth2AuthorizedClientManager); |
||||
} |
||||
|
||||
public OAuth2AccessTokenInterceptor(String tokenType, String header, String clientRegistrationId, |
||||
OAuth2AuthorizedClientManager oAuth2AuthorizedClientManager) { |
||||
this.tokenType = tokenType; |
||||
this.header = header; |
||||
this.clientRegistrationId = clientRegistrationId; |
||||
this.authorizedClientManager = oAuth2AuthorizedClientManager; |
||||
} |
||||
|
||||
@Override |
||||
public void apply(RequestTemplate template) { |
||||
OAuth2AccessToken token = getToken(template); |
||||
String extractedToken = String.format("%s %s", tokenType, token.getTokenValue()); |
||||
template.header(header); |
||||
template.header(header, extractedToken); |
||||
} |
||||
|
||||
public OAuth2AccessToken getToken(RequestTemplate template) { |
||||
// If specified, try to use them to get token.
|
||||
if (StringUtils.hasText(clientRegistrationId)) { |
||||
OAuth2AccessToken token = getToken(clientRegistrationId); |
||||
if (token != null) { |
||||
return token; |
||||
} |
||||
} |
||||
|
||||
// If not specified use host (synonymous with serviceId for load-balanced
|
||||
// requests; non-load-balanced requests should use the method above).
|
||||
OAuth2AccessToken token = getToken(getServiceId(template)); |
||||
if (token != null) { |
||||
return token; |
||||
} |
||||
throw new IllegalStateException("OAuth2 token has not been successfully acquired."); |
||||
} |
||||
|
||||
protected OAuth2AccessToken getToken(String clientRegistrationId) { |
||||
if (!StringUtils.hasText(clientRegistrationId)) { |
||||
return null; |
||||
} |
||||
|
||||
Authentication principal = SecurityContextHolder.getContext().getAuthentication(); |
||||
if (principal == null) { |
||||
principal = ANONYMOUS_AUTHENTICATION; |
||||
} |
||||
|
||||
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId) |
||||
.principal(principal).build(); |
||||
OAuth2AuthorizedClient authorizedClient = authorizedClientManager.authorize(authorizeRequest); |
||||
return Optional.ofNullable(authorizedClient).map(OAuth2AuthorizedClient::getAccessToken).orElse(null); |
||||
} |
||||
|
||||
private static String getServiceId(RequestTemplate template) { |
||||
Target<?> feignTarget = template.feignTarget(); |
||||
Assert.notNull(feignTarget, "FeignTarget may not be null."); |
||||
String url = feignTarget.url(); |
||||
Assert.hasLength(url, "Url may not be empty."); |
||||
final URI originalUri = URI.create(url); |
||||
return originalUri.getHost(); |
||||
} |
||||
|
||||
} |
@ -1,183 +0,0 @@
@@ -1,183 +0,0 @@
|
||||
/* |
||||
* 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.util.Arrays; |
||||
|
||||
import feign.RequestInterceptor; |
||||
import feign.RequestTemplate; |
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext; |
||||
import org.springframework.security.oauth2.client.http.AccessTokenRequiredException; |
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; |
||||
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest; |
||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider; |
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken; |
||||
|
||||
/** |
||||
* Pre-defined custom RequestInterceptor for Feign Requests. It uses the |
||||
* {@link OAuth2ClientContext OAuth2ClientContext} provided from the environment and |
||||
* construct a new header on the request before it is made by Feign. |
||||
* |
||||
* @author Joao Pedro Evangelista |
||||
* @author Tim Ysewyn |
||||
* @since 3.0.0 |
||||
*/ |
||||
@Deprecated // spring-security-oauth2 reached EOL
|
||||
public class OAuth2FeignRequestInterceptor 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 OAuth2ClientContext oAuth2ClientContext; |
||||
|
||||
private final OAuth2ProtectedResourceDetails resource; |
||||
|
||||
private final String tokenType; |
||||
|
||||
private final String header; |
||||
|
||||
private AccessTokenProvider accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider>asList( |
||||
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(), |
||||
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider())); |
||||
|
||||
/** |
||||
* Default constructor which uses the provided OAuth2ClientContext and Bearer tokens |
||||
* within Authorization header. |
||||
* @param oAuth2ClientContext provided context |
||||
* @param resource type of resource to be accessed |
||||
*/ |
||||
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, |
||||
OAuth2ProtectedResourceDetails resource) { |
||||
this(oAuth2ClientContext, resource, BEARER, AUTHORIZATION); |
||||
} |
||||
|
||||
/** |
||||
* Fully customizable constructor for changing token type and header name, in cases of |
||||
* Bearer and Authorization is not the default such as "bearer", "authorization". |
||||
* @param oAuth2ClientContext current oAuth2 Context |
||||
* @param resource type of resource to be accessed |
||||
* @param tokenType type of token e.g. "token", "Bearer" |
||||
* @param header name of the header e.g. "Authorization", "authorization" |
||||
*/ |
||||
public OAuth2FeignRequestInterceptor(OAuth2ClientContext oAuth2ClientContext, |
||||
OAuth2ProtectedResourceDetails resource, String tokenType, String header) { |
||||
this.oAuth2ClientContext = oAuth2ClientContext; |
||||
this.resource = resource; |
||||
this.tokenType = tokenType; |
||||
this.header = header; |
||||
} |
||||
|
||||
/** |
||||
* Create a template with the header of provided name and extracted extract. |
||||
* |
||||
* @see RequestInterceptor#apply(RequestTemplate) |
||||
*/ |
||||
@Override |
||||
public void apply(RequestTemplate template) { |
||||
template.header(header); // Clears out the header, no "clear" method available.
|
||||
template.header(header, extract(tokenType)); |
||||
} |
||||
|
||||
/** |
||||
* Extracts the token extract id the access token exists or returning an empty extract |
||||
* if there is no one on the context it may occasionally causes Unauthorized response |
||||
* since the token extract is empty. |
||||
* @param tokenType type name of token |
||||
* @return token value from context if it exists otherwise empty String |
||||
*/ |
||||
protected String extract(String tokenType) { |
||||
OAuth2AccessToken accessToken = getToken(); |
||||
return String.format("%s %s", tokenType, accessToken.getValue()); |
||||
} |
||||
|
||||
/** |
||||
* Extract the access token within the request or try to acquire a new one by |
||||
* delegating it to {@link #acquireAccessToken()}. |
||||
* @return valid token |
||||
*/ |
||||
public OAuth2AccessToken getToken() { |
||||
|
||||
OAuth2AccessToken accessToken = oAuth2ClientContext.getAccessToken(); |
||||
if (accessToken == null || accessToken.isExpired()) { |
||||
try { |
||||
accessToken = acquireAccessToken(); |
||||
} |
||||
catch (UserRedirectRequiredException e) { |
||||
oAuth2ClientContext.setAccessToken(null); |
||||
String stateKey = e.getStateKey(); |
||||
if (stateKey != null) { |
||||
Object stateToPreserve = e.getStateToPreserve(); |
||||
if (stateToPreserve == null) { |
||||
stateToPreserve = "NONE"; |
||||
} |
||||
oAuth2ClientContext.setPreservedState(stateKey, stateToPreserve); |
||||
} |
||||
throw e; |
||||
} |
||||
} |
||||
return accessToken; |
||||
} |
||||
|
||||
/** |
||||
* Try to acquire the token using a access token provider. |
||||
* @return valid access token |
||||
* @throws UserRedirectRequiredException in case the user needs to be redirected to an |
||||
* approval page or login page |
||||
*/ |
||||
protected OAuth2AccessToken acquireAccessToken() throws UserRedirectRequiredException { |
||||
AccessTokenRequest tokenRequest = oAuth2ClientContext.getAccessTokenRequest(); |
||||
if (tokenRequest == null) { |
||||
throw new AccessTokenRequiredException( |
||||
"Cannot find valid context on request for resource '" + resource.getId() + "'.", resource); |
||||
} |
||||
String stateKey = tokenRequest.getStateKey(); |
||||
if (stateKey != null) { |
||||
tokenRequest.setPreservedState(oAuth2ClientContext.removePreservedState(stateKey)); |
||||
} |
||||
OAuth2AccessToken existingToken = oAuth2ClientContext.getAccessToken(); |
||||
if (existingToken != null) { |
||||
oAuth2ClientContext.setAccessToken(existingToken); |
||||
} |
||||
OAuth2AccessToken obtainableAccessToken; |
||||
obtainableAccessToken = accessTokenProvider.obtainAccessToken(resource, tokenRequest); |
||||
if (obtainableAccessToken == null || obtainableAccessToken.getValue() == null) { |
||||
throw new IllegalStateException( |
||||
" Access token provider returned a null token, which is illegal according to the contract."); |
||||
} |
||||
oAuth2ClientContext.setAccessToken(obtainableAccessToken); |
||||
return obtainableAccessToken; |
||||
} |
||||
|
||||
public void setAccessTokenProvider(AccessTokenProvider accessTokenProvider) { |
||||
this.accessTokenProvider = accessTokenProvider; |
||||
} |
||||
|
||||
} |
@ -1,81 +0,0 @@
@@ -1,81 +0,0 @@
|
||||
/* |
||||
* 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.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor; |
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext; |
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenProviderChain; |
||||
import org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport; |
||||
import org.springframework.security.oauth2.client.token.grant.client.ClientCredentialsAccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.grant.implicit.ImplicitAccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.grant.password.ResourceOwnerPasswordAccessTokenProvider; |
||||
|
||||
/** |
||||
* Allows to customize pre-defined {@link OAuth2FeignRequestInterceptor} using configurer |
||||
* beans of class {@link OAuth2FeignRequestInterceptorConfigurer}. Each configurer |
||||
* instance can add {@link AccessTokenProvider} new {@link ClientHttpRequestInterceptor} |
||||
* instances. |
||||
* |
||||
* @author Wojciech Mąka |
||||
* @since 3.1.1 |
||||
*/ |
||||
public class OAuth2FeignRequestInterceptorBuilder { |
||||
|
||||
private AccessTokenProvider accessTokenProvider; |
||||
|
||||
private final List<ClientHttpRequestInterceptor> accessTokenProviderInterceptors = new ArrayList<>(); |
||||
|
||||
public OAuth2FeignRequestInterceptorBuilder() { |
||||
accessTokenProvider = new AccessTokenProviderChain(Arrays.<AccessTokenProvider>asList( |
||||
new AuthorizationCodeAccessTokenProvider(), new ImplicitAccessTokenProvider(), |
||||
new ResourceOwnerPasswordAccessTokenProvider(), new ClientCredentialsAccessTokenProvider())); |
||||
} |
||||
|
||||
public OAuth2FeignRequestInterceptorBuilder withAccessTokenProviderInterceptors( |
||||
ClientHttpRequestInterceptor... interceptors) { |
||||
accessTokenProviderInterceptors.addAll(Arrays.asList(interceptors)); |
||||
return this; |
||||
} |
||||
|
||||
OAuth2FeignRequestInterceptor build(OAuth2ClientContext oAuth2ClientContext, |
||||
OAuth2ProtectedResourceDetails resource) { |
||||
if (OAuth2AccessTokenSupport.class.isAssignableFrom(accessTokenProvider.getClass())) { |
||||
((OAuth2AccessTokenSupport) accessTokenProvider).setInterceptors(accessTokenProviderInterceptors); |
||||
} |
||||
final OAuth2FeignRequestInterceptor feignRequestInterceptor = new OAuth2FeignRequestInterceptor( |
||||
oAuth2ClientContext, resource); |
||||
feignRequestInterceptor.setAccessTokenProvider(accessTokenProvider); |
||||
return feignRequestInterceptor; |
||||
} |
||||
|
||||
public static OAuth2FeignRequestInterceptor buildWithConfigurers(OAuth2ClientContext oAuth2ClientContext, |
||||
OAuth2ProtectedResourceDetails resource, List<OAuth2FeignRequestInterceptorConfigurer> buildConfigurers) { |
||||
final OAuth2FeignRequestInterceptorBuilder builder = new OAuth2FeignRequestInterceptorBuilder(); |
||||
for (OAuth2FeignRequestInterceptorConfigurer configurer : buildConfigurers) { |
||||
configurer.customize(builder); |
||||
} |
||||
return builder.build(oAuth2ClientContext, resource); |
||||
} |
||||
|
||||
} |
@ -1,35 +0,0 @@
@@ -1,35 +0,0 @@
|
||||
/* |
||||
* 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 org.springframework.security.oauth2.client.token.AccessTokenProvider; |
||||
|
||||
/** |
||||
* Interface for configurer beans working with |
||||
* {@link OAuth2FeignRequestInterceptorBuilder} in order to provide custom interceptors |
||||
* for {@link AccessTokenProvider} managed internally by |
||||
* {@link OAuth2FeignRequestInterceptor}. |
||||
* |
||||
* @author Wojciech Mąka |
||||
* @since 3.1.1 |
||||
*/ |
||||
@FunctionalInterface |
||||
public interface OAuth2FeignRequestInterceptorConfigurer { |
||||
|
||||
void customize(OAuth2FeignRequestInterceptorBuilder requestInterceptorBuilder); |
||||
|
||||
} |
@ -1,96 +0,0 @@
@@ -1,96 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-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 jakarta.servlet.http.HttpServletRequest; |
||||
import org.junit.jupiter.api.Disabled; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; |
||||
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor; |
||||
import org.springframework.cloud.openfeign.EnableFeignClients; |
||||
import org.springframework.cloud.openfeign.FeignClient; |
||||
import org.springframework.cloud.openfeign.FeignContext; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.test.annotation.DirtiesContext; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; |
||||
|
||||
/** |
||||
* @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" }) |
||||
@DirtiesContext |
||||
public class AccessTokenProviderWithLoadBalancerInterceptorTests { |
||||
|
||||
@Autowired |
||||
FeignContext context; |
||||
|
||||
@Autowired |
||||
private ConfigurableApplicationContext applicationContext; |
||||
|
||||
@Test |
||||
@Disabled |
||||
void testOAuth2RequestInterceptorIsLoadBalanced() { |
||||
AssertableApplicationContext assertableContext = AssertableApplicationContext.get(() -> applicationContext); |
||||
assertThat(assertableContext).hasSingleBean(Application.SampleClient.class); |
||||
assertThat(assertableContext).hasSingleBean(OAuth2FeignRequestInterceptor.class); |
||||
assertThat(assertableContext).getBean(OAuth2FeignRequestInterceptor.class).extracting("accessTokenProvider") |
||||
.extracting("interceptors").asList() |
||||
.filteredOn(obj -> RetryLoadBalancerInterceptor.class.equals(obj.getClass())).hasSize(1); |
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
@EnableAutoConfiguration |
||||
@RestController |
||||
@EnableFeignClients( |
||||
clients = { AccessTokenProviderWithLoadBalancerInterceptorTests.Application.SampleClient.class }) |
||||
protected static class Application { |
||||
|
||||
@GetMapping("/foo") |
||||
public String foo(HttpServletRequest request) throws IllegalAccessException { |
||||
if ("Foo".equals(request.getHeader("Foo")) && "Bar".equals(request.getHeader("Bar"))) { |
||||
return "OK"; |
||||
} |
||||
else { |
||||
throw new IllegalAccessException("It should has Foo and Bar header"); |
||||
} |
||||
} |
||||
|
||||
@FeignClient(name = "sampleClient") |
||||
protected interface SampleClient { |
||||
|
||||
@GetMapping("/foo") |
||||
String foo(); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -1,96 +0,0 @@
@@ -1,96 +0,0 @@
|
||||
/* |
||||
* Copyright 2013-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 jakarta.servlet.http.HttpServletRequest; |
||||
import org.junit.jupiter.api.Disabled; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
import org.springframework.boot.test.context.assertj.AssertableApplicationContext; |
||||
import org.springframework.cloud.client.loadbalancer.RetryLoadBalancerInterceptor; |
||||
import org.springframework.cloud.openfeign.EnableFeignClients; |
||||
import org.springframework.cloud.openfeign.FeignClient; |
||||
import org.springframework.cloud.openfeign.FeignContext; |
||||
import org.springframework.context.ConfigurableApplicationContext; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.test.annotation.DirtiesContext; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; |
||||
|
||||
/** |
||||
* @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" }) |
||||
@DirtiesContext |
||||
public class AccessTokenProviderWithoutLoadBalancerInterceptorTests { |
||||
|
||||
@Autowired |
||||
FeignContext context; |
||||
|
||||
@Autowired |
||||
private ConfigurableApplicationContext applicationContext; |
||||
|
||||
@Test |
||||
@Disabled |
||||
void testOAuth2RequestInterceptorIsNotLoadBalanced() { |
||||
AssertableApplicationContext assertableContext = AssertableApplicationContext.get(() -> applicationContext); |
||||
assertThat(assertableContext) |
||||
.hasSingleBean(AccessTokenProviderWithoutLoadBalancerInterceptorTests.Application.SampleClient.class); |
||||
assertThat(assertableContext).hasSingleBean(OAuth2FeignRequestInterceptor.class); |
||||
assertThat(assertableContext).getBean(OAuth2FeignRequestInterceptor.class).extracting("accessTokenProvider") |
||||
.extracting("interceptors").asList() |
||||
.filteredOn(obj -> RetryLoadBalancerInterceptor.class.equals(obj.getClass())).isEmpty(); |
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
@EnableAutoConfiguration |
||||
@RestController |
||||
@EnableFeignClients( |
||||
clients = { AccessTokenProviderWithoutLoadBalancerInterceptorTests.Application.SampleClient.class }) |
||||
protected static class Application { |
||||
|
||||
@GetMapping("/foo") |
||||
public String foo(HttpServletRequest request) throws IllegalAccessException { |
||||
if ("Foo".equals(request.getHeader("Foo")) && "Bar".equals(request.getHeader("Bar"))) { |
||||
return "OK"; |
||||
} |
||||
else { |
||||
throw new IllegalAccessException("It should has Foo and Bar header"); |
||||
} |
||||
} |
||||
|
||||
@FeignClient(name = "sampleClient") |
||||
protected interface SampleClient { |
||||
|
||||
@GetMapping("/foo") |
||||
String foo(); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
} |
@ -1,65 +0,0 @@
@@ -1,65 +0,0 @@
|
||||
/* |
||||
* 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 org.springframework.security.access.AccessDeniedException; |
||||
import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails; |
||||
import org.springframework.security.oauth2.client.resource.UserApprovalRequiredException; |
||||
import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenProvider; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest; |
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken; |
||||
|
||||
/** |
||||
* Mocks the access token provider |
||||
* |
||||
* @author Mihhail Verhovtsov |
||||
*/ |
||||
public class MockAccessTokenProvider implements AccessTokenProvider { |
||||
|
||||
private OAuth2AccessToken token; |
||||
|
||||
public MockAccessTokenProvider(OAuth2AccessToken token) { |
||||
this.token = token; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessToken obtainAccessToken(OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails, |
||||
AccessTokenRequest accessTokenRequest) |
||||
throws UserRedirectRequiredException, UserApprovalRequiredException, AccessDeniedException { |
||||
return token; |
||||
} |
||||
|
||||
@Override |
||||
public boolean supportsResource(OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails) { |
||||
return true; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessToken refreshAccessToken(OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails, |
||||
OAuth2RefreshToken oAuth2RefreshToken, AccessTokenRequest accessTokenRequest) |
||||
throws UserRedirectRequiredException { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public boolean supportsRefresh(OAuth2ProtectedResourceDetails oAuth2ProtectedResourceDetails) { |
||||
return false; |
||||
} |
||||
|
||||
} |
@ -1,79 +0,0 @@
@@ -1,79 +0,0 @@
|
||||
/* |
||||
* 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.util.Date; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken; |
||||
import org.springframework.security.oauth2.common.OAuth2RefreshToken; |
||||
|
||||
/** |
||||
* Mocks the OAuth2 access token |
||||
* |
||||
* @author Mihhail Verhovtsov |
||||
*/ |
||||
public class MockOAuth2AccessToken implements OAuth2AccessToken { |
||||
|
||||
private String value; |
||||
|
||||
public MockOAuth2AccessToken(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
@Override |
||||
public Map<String, Object> getAdditionalInformation() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public Set<String> getScope() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2RefreshToken getRefreshToken() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public String getTokenType() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public boolean isExpired() { |
||||
return false; |
||||
} |
||||
|
||||
@Override |
||||
public Date getExpiration() { |
||||
return null; |
||||
} |
||||
|
||||
@Override |
||||
public int getExpiresIn() { |
||||
return 0; |
||||
} |
||||
|
||||
@Override |
||||
public String getValue() { |
||||
return value; |
||||
} |
||||
|
||||
} |
@ -1,67 +0,0 @@
@@ -1,67 +0,0 @@
|
||||
/* |
||||
* 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.util.HashMap; |
||||
|
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest; |
||||
import org.springframework.security.oauth2.client.token.DefaultAccessTokenRequest; |
||||
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken; |
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken; |
||||
|
||||
/** |
||||
* Mocks the current client context |
||||
* |
||||
* @author João Pedro Evangelista |
||||
*/ |
||||
public final class MockOAuth2ClientContext implements OAuth2ClientContext { |
||||
|
||||
private final String value; |
||||
|
||||
MockOAuth2ClientContext(String value) { |
||||
this.value = value; |
||||
} |
||||
|
||||
@Override |
||||
public OAuth2AccessToken getAccessToken() { |
||||
return new DefaultOAuth2AccessToken(value); |
||||
} |
||||
|
||||
@Override |
||||
public void setAccessToken(OAuth2AccessToken accessToken) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public AccessTokenRequest getAccessTokenRequest() { |
||||
DefaultAccessTokenRequest tokenRequest = new DefaultAccessTokenRequest(new HashMap<String, String[]>()); |
||||
tokenRequest.setExistingToken(new DefaultOAuth2AccessToken(value)); |
||||
return tokenRequest; |
||||
} |
||||
|
||||
@Override |
||||
public void setPreservedState(String stateKey, Object preservedState) { |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public Object removePreservedState(String stateKey) { |
||||
return null; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,128 @@
@@ -0,0 +1,128 @@
|
||||
/* |
||||
* 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 feign.Request.HttpMethod; |
||||
import feign.RequestTemplate; |
||||
import feign.Target; |
||||
import org.junit.jupiter.api.BeforeEach; |
||||
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.registration.ClientRegistration; |
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; |
||||
import org.springframework.security.oauth2.core.OAuth2AccessToken; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.ArgumentMatchers.argThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
/** |
||||
* Tests for {@link OAuth2AccessTokenInterceptor}. |
||||
* |
||||
* @author Dangzhicairang(小水牛) |
||||
* @author Olga Maciaszek-Sharma |
||||
* |
||||
*/ |
||||
class OAuth2AccessTokenInterceptorTests { |
||||
|
||||
private final OAuth2AuthorizedClientManager mockOAuth2AuthorizedClientManager = mock( |
||||
OAuth2AuthorizedClientManager.class); |
||||
|
||||
private OAuth2AccessTokenInterceptor oAuth2AccessTokenInterceptor; |
||||
|
||||
private RequestTemplate requestTemplate; |
||||
|
||||
private static final String DEFAULT_CLIENT_REGISTRATION_ID = "feign-client"; |
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
requestTemplate = new RequestTemplate().method(HttpMethod.GET); |
||||
Target<?> feignTarget = mock(Target.class); |
||||
when(feignTarget.url()).thenReturn("http://test"); |
||||
requestTemplate.feignTarget(feignTarget); |
||||
} |
||||
|
||||
@Test |
||||
void shouldThrowExceptionWhenNoTokenAcquired() { |
||||
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); |
||||
when(mockOAuth2AuthorizedClientManager.authorize(any())).thenReturn(null); |
||||
|
||||
assertThatExceptionOfType(IllegalStateException.class) |
||||
.isThrownBy(() -> oAuth2AccessTokenInterceptor.apply(requestTemplate)) |
||||
.withMessage("OAuth2 token has not been successfully acquired."); |
||||
} |
||||
|
||||
@Test |
||||
void shouldAcquireValidToken() { |
||||
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); |
||||
when(mockOAuth2AuthorizedClientManager.authorize( |
||||
argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId())))) |
||||
.thenReturn(validTokenOAuth2AuthorizedClient()); |
||||
|
||||
oAuth2AccessTokenInterceptor.apply(requestTemplate); |
||||
|
||||
assertThat(requestTemplate.headers().get("Authorization")).contains("Bearer Valid Token"); |
||||
} |
||||
|
||||
@Test |
||||
void shouldAcquireValidTokenFromServiceId() { |
||||
when(mockOAuth2AuthorizedClientManager.authorize( |
||||
argThat((OAuth2AuthorizeRequest request) -> ("test").equals(request.getClientRegistrationId())))) |
||||
.thenReturn(validTokenOAuth2AuthorizedClient()); |
||||
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(mockOAuth2AuthorizedClientManager); |
||||
|
||||
oAuth2AccessTokenInterceptor.apply(requestTemplate); |
||||
|
||||
assertThat(requestTemplate.headers().get("Authorization")).contains("Bearer Valid Token"); |
||||
} |
||||
|
||||
@Test |
||||
void shouldAcquireValidTokenFromSpecifiedClientRegistrationId() { |
||||
oAuth2AccessTokenInterceptor = new OAuth2AccessTokenInterceptor(DEFAULT_CLIENT_REGISTRATION_ID, |
||||
mockOAuth2AuthorizedClientManager); |
||||
when(mockOAuth2AuthorizedClientManager |
||||
.authorize(argThat((OAuth2AuthorizeRequest request) -> (DEFAULT_CLIENT_REGISTRATION_ID) |
||||
.equals(request.getClientRegistrationId())))).thenReturn(validTokenOAuth2AuthorizedClient()); |
||||
|
||||
oAuth2AccessTokenInterceptor.apply(requestTemplate); |
||||
|
||||
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 OAuth2AuthorizedClient validTokenOAuth2AuthorizedClient() { |
||||
return new OAuth2AuthorizedClient(defaultClientRegistration(), "anonymousUser", validToken()); |
||||
} |
||||
|
||||
private ClientRegistration defaultClientRegistration() { |
||||
return ClientRegistration.withRegistrationId(DEFAULT_CLIENT_REGISTRATION_ID).clientId("clientId") |
||||
.tokenUri("mock token uri").authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS).build(); |
||||
} |
||||
|
||||
} |
@ -1,118 +0,0 @@
@@ -1,118 +0,0 @@
|
||||
/* |
||||
* 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.util.Collection; |
||||
import java.util.Map; |
||||
|
||||
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.security.oauth2.client.DefaultOAuth2ClientContext; |
||||
import org.springframework.security.oauth2.client.OAuth2ClientContext; |
||||
import org.springframework.security.oauth2.client.resource.BaseOAuth2ProtectedResourceDetails; |
||||
import org.springframework.security.oauth2.client.resource.OAuth2AccessDeniedException; |
||||
import org.springframework.security.oauth2.client.token.AccessTokenRequest; |
||||
import org.springframework.security.oauth2.common.OAuth2AccessToken; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.when; |
||||
|
||||
/** |
||||
* @author João Pedro Evangelista |
||||
* @author Tim Ysewyn |
||||
* @author Szymon Linowski |
||||
*/ |
||||
class OAuth2FeignRequestInterceptorTests { |
||||
|
||||
private OAuth2FeignRequestInterceptor oAuth2FeignRequestInterceptor; |
||||
|
||||
private RequestTemplate requestTemplate; |
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
oAuth2FeignRequestInterceptor = new OAuth2FeignRequestInterceptor(new MockOAuth2ClientContext("Fancy"), |
||||
new BaseOAuth2ProtectedResourceDetails()); |
||||
requestTemplate = new RequestTemplate().method(HttpMethod.GET); |
||||
} |
||||
|
||||
@Test |
||||
void applyAuthorizationHeader() { |
||||
oAuth2FeignRequestInterceptor.apply(requestTemplate); |
||||
Map<String, Collection<String>> headers = requestTemplate.headers(); |
||||
|
||||
assertThat(headers.containsKey("Authorization")).describedAs("RequestTemplate must have a Authorization header") |
||||
.isTrue(); |
||||
Assertions.assertThat(headers.get("Authorization")).describedAs("Authorization must have a extract of Fancy") |
||||
.contains("Bearer Fancy"); |
||||
} |
||||
|
||||
@Test |
||||
void tryToAcquireToken() { |
||||
oAuth2FeignRequestInterceptor = new OAuth2FeignRequestInterceptor(new DefaultOAuth2ClientContext(), |
||||
new BaseOAuth2ProtectedResourceDetails()); |
||||
|
||||
Assertions.assertThatExceptionOfType(OAuth2AccessDeniedException.class) |
||||
.isThrownBy(() -> oAuth2FeignRequestInterceptor.getToken()).withMessage( |
||||
"Unable to obtain a new access token for resource 'null'. The provider manager is not configured to support it."); |
||||
} |
||||
|
||||
@Test |
||||
void configureAccessTokenProvider() { |
||||
OAuth2AccessToken mockedToken = new MockOAuth2AccessToken("MOCKED_TOKEN"); |
||||
oAuth2FeignRequestInterceptor.setAccessTokenProvider(new MockAccessTokenProvider(mockedToken)); |
||||
|
||||
assertThat(oAuth2FeignRequestInterceptor.acquireAccessToken()) |
||||
.describedAs("Should return same mocked token instance").isEqualTo(mockedToken); |
||||
} |
||||
|
||||
@Test |
||||
void applyAuthorizationHeaderOnlyOnce() { |
||||
OAuth2ClientContext oAuth2ClientContext = mock(OAuth2ClientContext.class); |
||||
when(oAuth2ClientContext.getAccessToken()).thenReturn(new MockOAuth2AccessToken("MOCKED_TOKEN")); |
||||
|
||||
OAuth2FeignRequestInterceptor oAuth2FeignRequestInterceptor = new OAuth2FeignRequestInterceptor( |
||||
oAuth2ClientContext, new BaseOAuth2ProtectedResourceDetails()); |
||||
|
||||
oAuth2FeignRequestInterceptor.apply(requestTemplate); |
||||
|
||||
// First idempotent call failed, retry mechanism kicks in, and token has expired
|
||||
// in the meantime
|
||||
|
||||
OAuth2AccessToken expiredAccessToken = mock(OAuth2AccessToken.class); |
||||
when(expiredAccessToken.isExpired()).thenReturn(true); |
||||
when(oAuth2ClientContext.getAccessToken()).thenReturn(expiredAccessToken); |
||||
AccessTokenRequest accessTokenRequest = mock(AccessTokenRequest.class); |
||||
when(oAuth2ClientContext.getAccessTokenRequest()).thenReturn(accessTokenRequest); |
||||
OAuth2AccessToken newToken = new MockOAuth2AccessToken("Fancy"); |
||||
oAuth2FeignRequestInterceptor.setAccessTokenProvider(new MockAccessTokenProvider(newToken)); |
||||
|
||||
oAuth2FeignRequestInterceptor.apply(requestTemplate); |
||||
|
||||
Map<String, Collection<String>> headers = requestTemplate.headers(); |
||||
assertThat(headers.containsKey("Authorization")).describedAs("RequestTemplate must have a Authorization header") |
||||
.isTrue(); |
||||
assertThat(headers.get("Authorization")).describedAs("Authorization must have a extract of Fancy").hasSize(1); |
||||
assertThat(headers.get("Authorization")).describedAs("Authorization must have a extract of Fancy") |
||||
.contains("Bearer Fancy"); |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue