From 139cc9dab4d88166544087a958cb295c7c682e95 Mon Sep 17 00:00:00 2001 From: Ryan Baxter Date: Thu, 22 Feb 2018 15:43:04 -0500 Subject: [PATCH] Changes and new tests from Netflix --- pom.xml | 9 +- spring-cloud-openfeign-core/pom.xml | 5 + .../security/HystrixSecurityApplication.java | 32 ++++++ .../security/HystrixSecurityTests.java | 100 ++++++++++++++++++ .../app/CustomConcurrenyStrategy.java | 21 ++++ .../security/app/ProxyUsernameController.java | 36 +++++++ .../hystrix/security/app/TestInterceptor.java | 40 +++++++ .../hystrix/security/app/UsernameClient.java | 30 ++++++ .../security/app/UsernameController.java | 33 ++++++ ...ignRibbonHttpClientConfigurationTests.java | 14 +-- ...nRibbonOkHttpClientConfigurationTests.java | 14 +-- spring-cloud-openfeign-dependencies/pom.xml | 5 + spring-cloud-starter-openfeign/pom.xml | 4 +- .../main/resources/META-INF/spring.provides | 2 +- 14 files changed, 329 insertions(+), 16 deletions(-) create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityApplication.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityTests.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/CustomConcurrenyStrategy.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/ProxyUsernameController.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/TestInterceptor.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameClient.java create mode 100644 spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameController.java diff --git a/pom.xml b/pom.xml index c69a9cc9..24720503 100644 --- a/pom.xml +++ b/pom.xml @@ -70,7 +70,7 @@ org.springframework.cloud spring-cloud-netflix-dependencies - ${project.version} + ${spring-cloud-netflix.version} pom import @@ -81,6 +81,13 @@ pom import + + org.springframework.cloud + spring-cloud-openfeign-dependencies + ${project.version} + pom + import + org.springframework.cloud spring-cloud-test-support diff --git a/spring-cloud-openfeign-core/pom.xml b/spring-cloud-openfeign-core/pom.xml index 13373977..9a910f62 100644 --- a/spring-cloud-openfeign-core/pom.xml +++ b/spring-cloud-openfeign-core/pom.xml @@ -183,6 +183,11 @@ ribbon test + + org.springframework.cloud + spring-cloud-starter-netflix-hystrix + test + diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityApplication.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityApplication.java new file mode 100644 index 00000000..58ddb69e --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright 2013-2016 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 + * + * http://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.hystrix.security; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.openfeign.EnableFeignClients; +import org.springframework.cloud.openfeign.hystrix.security.app.UsernameClient; +import org.springframework.context.annotation.Configuration; + +/** + * @author Daniel Lavoie + */ +@Configuration +@SpringBootApplication +@EnableFeignClients(clients = UsernameClient.class) +public class HystrixSecurityApplication { + +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityTests.java new file mode 100644 index 00000000..5276b2b5 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/HystrixSecurityTests.java @@ -0,0 +1,100 @@ +/* + * Copyright 2013-2016 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 + * + * http://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.hystrix.security; + +import java.util.Base64; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.cloud.netflix.hystrix.security.SecurityContextConcurrencyStrategy; +import org.springframework.cloud.openfeign.hystrix.security.app.CustomConcurrenyStrategy; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestTemplate; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that a secured web service returning values using a feign client properly access + * the security context from a hystrix command. + * @author Daniel Lavoie + */ +@RunWith(SpringRunner.class) +@DirtiesContext +@SpringBootTest(classes = HystrixSecurityApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT, + properties = { "username.ribbon.listOfServers=localhost:${local.server.port}", + "feign.hystrix.enabled=true"}) +@ActiveProfiles("proxysecurity") +public class HystrixSecurityTests { + @Autowired + private CustomConcurrenyStrategy customConcurrenyStrategy; + + @LocalServerPort + private String serverPort; + + //TODOO: move to constants in TestAutoConfiguration + private String username = "user"; + + private String password = "password"; + + @Test + public void testSecurityConcurrencyStrategyInstalled() { + HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy(); + assertThat(concurrencyStrategy).isInstanceOf(SecurityContextConcurrencyStrategy.class); + } + + @Test + public void testFeignHystrixSecurity() { + HttpHeaders headers = HystrixSecurityTests.createBasicAuthHeader(username, + password); + + String usernameResult = new RestTemplate() + .exchange("http://localhost:" + serverPort + "/proxy-username", + HttpMethod.GET, new HttpEntity(headers), String.class) + .getBody(); + + Assert.assertTrue("Username should have been intercepted by feign interceptor.", + username.equals(usernameResult)); + + Assert.assertTrue("Custom hook should have been called.", + customConcurrenyStrategy.isHookCalled()); + } + + public static HttpHeaders createBasicAuthHeader(final String username, + final String password) { + return new HttpHeaders() { + private static final long serialVersionUID = 1766341693637204893L; + + { + String auth = username + ":" + password; + byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes()); + String authHeader = "Basic " + new String(encodedAuth); + this.set("Authorization", authHeader); + } + }; + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/CustomConcurrenyStrategy.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/CustomConcurrenyStrategy.java new file mode 100644 index 00000000..b32657c4 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/CustomConcurrenyStrategy.java @@ -0,0 +1,21 @@ +package org.springframework.cloud.openfeign.hystrix.security.app; + +import java.util.concurrent.Callable; +import org.springframework.stereotype.Component; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; + +@Component +public class CustomConcurrenyStrategy extends HystrixConcurrencyStrategy { + private boolean hookCalled; + + @Override + public Callable wrapCallable(Callable callable) { + this.hookCalled = true; + + return super.wrapCallable(callable); + } + + public boolean isHookCalled() { + return hookCalled; + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/ProxyUsernameController.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/ProxyUsernameController.java new file mode 100644 index 00000000..bf4ccd5b --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/ProxyUsernameController.java @@ -0,0 +1,36 @@ +/* + * Copyright 2013-2016 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 + * + * http://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.hystrix.security.app; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Daniel Lavoie + */ +@RestController +@RequestMapping("/proxy-username") +public class ProxyUsernameController { + @Autowired + private UsernameClient usernameClient; + + @RequestMapping + public String getUsername() { + return usernameClient.getUsername(); + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/TestInterceptor.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/TestInterceptor.java new file mode 100644 index 00000000..c41a8cbb --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/TestInterceptor.java @@ -0,0 +1,40 @@ +/* + * Copyright 2013-2016 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 + * + * http://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.hystrix.security.app; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +/** + * This interceptor should be called from an Hyxtrix command execution thread. It is + * access the SecurityContext and settings an http header from the authentication details. + * + * @author Daniel Lavoie + */ +@Component +public class TestInterceptor implements RequestInterceptor { + + @Override + public void apply(RequestTemplate template) { + if (SecurityContextHolder.getContext().getAuthentication() != null) + template.header("username", + SecurityContextHolder.getContext().getAuthentication().getName()); + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameClient.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameClient.java new file mode 100644 index 00000000..1d245b7c --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameClient.java @@ -0,0 +1,30 @@ +/* + * Copyright 2013-2016 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 + * + * http://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.hystrix.security.app; + +import org.springframework.cloud.openfeign.FeignClient; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author Daniel Lavoie + */ +@FeignClient("username") +public interface UsernameClient { + + @RequestMapping("/username") + public String getUsername(); +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameController.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameController.java new file mode 100644 index 00000000..daacc0d5 --- /dev/null +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/hystrix/security/app/UsernameController.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2016 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 + * + * http://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.hystrix.security.app; + +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author Daniel Lavoie + */ +@RestController +@RequestMapping("/username") +public class UsernameController { + @RequestMapping + public String getUsername(@RequestHeader String username){ + return username; + } +} diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java index 4e35b5d5..c3eeb7c2 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonHttpClientConfigurationTests.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package org.springframework.cloud.openfeign.ribbon; +package org.springframework.cloud.netflix.feign.ribbon; import java.lang.reflect.Field; import javax.net.ssl.SSLContextSpi; @@ -30,10 +30,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientRetryTests; import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.ReflectionUtils; -import org.springframework.web.bind.annotation.RestController; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; @@ -42,8 +43,10 @@ import static org.junit.Assert.assertNull; * @author Ryan Baxter */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = FeignRibbonHttpClientConfigurationTests.Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, +@SpringBootTest(classes = FeignRibbonHttpClientConfigurationTests.FeignRibbonHttpClientConfigurationTestsApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {"debug=true","feign.httpclient.disableSslValidation=true"}) +@DirtiesContext public class FeignRibbonHttpClientConfigurationTests { @Autowired @@ -77,11 +80,10 @@ public class FeignRibbonHttpClientConfigurationTests { @Configuration @EnableAutoConfiguration - @RestController - public static class Application { + static class FeignRibbonHttpClientConfigurationTestsApplication { public static void main(String[] args) { new SpringApplicationBuilder(FeignRibbonClientRetryTests.Application.class) .run(args); } } -} +} \ No newline at end of file diff --git a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java index e05a3927..eb6956e5 100644 --- a/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java +++ b/spring-cloud-openfeign-core/src/test/java/org/springframework/cloud/openfeign/ribbon/FeignRibbonOkHttpClientConfigurationTests.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package org.springframework.cloud.openfeign.ribbon; +package org.springframework.cloud.netflix.feign.ribbon; import okhttp3.OkHttpClient; @@ -28,18 +28,21 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.commons.httpclient.OkHttpClientFactory; +import org.springframework.cloud.openfeign.ribbon.FeignRibbonClientRetryTests; import org.springframework.context.annotation.Configuration; +import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.ReflectionUtils; -import org.springframework.web.bind.annotation.RestController; /** * @author Ryan Baxter */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = FeignRibbonOkHttpClientConfigurationTests.Application.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, +@SpringBootTest(classes = FeignRibbonOkHttpClientConfigurationTests.FeignRibbonOkHttpClientConfigurationTestsApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, properties = {"debug=true","feign.httpclient.disableSslValidation=true", "feign.okhttp.enabled=true", "feign.httpclient.enabled=false"}) +@DirtiesContext public class FeignRibbonOkHttpClientConfigurationTests { @Autowired @@ -60,11 +63,10 @@ public class FeignRibbonOkHttpClientConfigurationTests { @Configuration @EnableAutoConfiguration - @RestController - public static class Application { + static class FeignRibbonOkHttpClientConfigurationTestsApplication { public static void main(String[] args) { new SpringApplicationBuilder(FeignRibbonClientRetryTests.Application.class) .run(args); } } -} +} \ No newline at end of file diff --git a/spring-cloud-openfeign-dependencies/pom.xml b/spring-cloud-openfeign-dependencies/pom.xml index aad79132..b8c18265 100644 --- a/spring-cloud-openfeign-dependencies/pom.xml +++ b/spring-cloud-openfeign-dependencies/pom.xml @@ -23,6 +23,11 @@ spring-cloud-openfeign-core ${project.version} + + org.springframework.cloud + spring-cloud-starter-openfeign + ${project.version} + io.github.openfeign feign-core diff --git a/spring-cloud-starter-openfeign/pom.xml b/spring-cloud-starter-openfeign/pom.xml index 607b9091..a9262118 100644 --- a/spring-cloud-starter-openfeign/pom.xml +++ b/spring-cloud-starter-openfeign/pom.xml @@ -5,6 +5,7 @@ org.springframework.cloud spring-cloud-openfeign 2.0.0.BUILD-SNAPSHOT + .. spring-cloud-starter-openfeign Spring Cloud Starter OpenFeign @@ -15,7 +16,7 @@ https://www.spring.io - ${basedir}/../../.. + ${basedir}/../.. @@ -25,7 +26,6 @@ org.springframework.cloud spring-cloud-openfeign-core - 2.0.0.BUILD-SNAPSHOT org.springframework diff --git a/spring-cloud-starter-openfeign/src/main/resources/META-INF/spring.provides b/spring-cloud-starter-openfeign/src/main/resources/META-INF/spring.provides index 3dc45a03..ee5b1c80 100644 --- a/spring-cloud-starter-openfeign/src/main/resources/META-INF/spring.provides +++ b/spring-cloud-starter-openfeign/src/main/resources/META-INF/spring.provides @@ -1 +1 @@ -provides: spring-platform-netflix-core, eureka-client \ No newline at end of file +provides: spring-cloud-openfeign-core \ No newline at end of file