diff --git a/docs/src/main/asciidoc/spring-cloud-netflix.adoc b/docs/src/main/asciidoc/spring-cloud-netflix.adoc index 141f986a..eeb71b9f 100644 --- a/docs/src/main/asciidoc/spring-cloud-netflix.adoc +++ b/docs/src/main/asciidoc/spring-cloud-netflix.adoc @@ -507,6 +507,8 @@ If you want some thread local context to propagate into a `@HystrixCommand` the The same thing applies if you are using `@SessionScope` or `@RequestScope`. You will know when you need to do this because of a runtime exception that says it can't find the scoped context. +You also have the option to set the `hystrix.shareSecurityContext` property to `true`. Doing so will auto configure an Hystrix concurrency strategy plugin hook who will transfer the `SecurityContext` from your main thread to the one used by the Hystrix command. Hystrix does not allow multiple hystrix concurrency strategy to be registered so an extension mechanism is available by declaring your own `HystrixConcurrencyStrategy` as a Spring bean. Spring Cloud will lookup for your implementation within the Spring context and wrap it inside its own plugin. + ### Health Indicator The state of the connected circuit breakers are also exposed in the diff --git a/spring-cloud-netflix-core/pom.xml b/spring-cloud-netflix-core/pom.xml index 91b38f2a..45c1e2de 100644 --- a/spring-cloud-netflix-core/pom.xml +++ b/spring-cloud-netflix-core/pom.xml @@ -34,6 +34,11 @@ spring-boot-starter-actuator true + + org.springframework.boot + spring-boot-starter-security + true + org.springframework.boot spring-boot-starter-web diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityAutoConfiguration.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityAutoConfiguration.java new file mode 100644 index 00000000..41442c9a --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityAutoConfiguration.java @@ -0,0 +1,87 @@ +/* + * 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.netflix.hystrix.security; + +import javax.annotation.PostConstruct; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.AllNestedConditions; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration.HystrixSecurityCondition; +import org.springframework.context.annotation.Conditional; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.context.SecurityContext; + +import com.netflix.hystrix.Hystrix; +import com.netflix.hystrix.strategy.HystrixPlugins; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.eventnotifier.HystrixEventNotifier; +import com.netflix.hystrix.strategy.executionhook.HystrixCommandExecutionHook; +import com.netflix.hystrix.strategy.metrics.HystrixMetricsPublisher; +import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy; + +/** + * @author Daniel Lavoie + */ +@Configuration +@Conditional(HystrixSecurityCondition.class) +@ConditionalOnClass({ Hystrix.class, SecurityContext.class }) +public class HystrixSecurityAutoConfiguration { + @Autowired(required = false) + private HystrixConcurrencyStrategy existingConcurrencyStrategy; + + @PostConstruct + public void init() { + // Keeps references of existing Hystrix plugins. + HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance() + .getEventNotifier(); + HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance() + .getMetricsPublisher(); + HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance() + .getPropertiesStrategy(); + HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance() + .getCommandExecutionHook(); + + HystrixPlugins.reset(); + + // Registers existing plugins excepts the Concurrent Strategy plugin. + HystrixPlugins.getInstance().registerConcurrencyStrategy( + new SecurityContextConcurrencyStrategy(existingConcurrencyStrategy)); + HystrixPlugins.getInstance().registerEventNotifier(eventNotifier); + HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher); + HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy); + HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook); + } + + static class HystrixSecurityCondition extends AllNestedConditions { + + public HystrixSecurityCondition() { + super(ConfigurationPhase.REGISTER_BEAN); + } + + @ConditionalOnProperty(name = "feign.hystrix.enabled", matchIfMissing = true) + static class HystrixEnabled { + + } + + @ConditionalOnProperty(name = "hystrix.shareSecurityContext") + static class ShareSecurityContext { + + } + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/security/SecurityContextConcurrencyStrategy.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/security/SecurityContextConcurrencyStrategy.java new file mode 100644 index 00000000..48079e74 --- /dev/null +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/hystrix/security/SecurityContextConcurrencyStrategy.java @@ -0,0 +1,78 @@ +/* + * 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.netflix.hystrix.security; + +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.springframework.security.concurrent.DelegatingSecurityContextCallable; + +import com.netflix.hystrix.HystrixThreadPoolKey; +import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariable; +import com.netflix.hystrix.strategy.concurrency.HystrixRequestVariableLifecycle; +import com.netflix.hystrix.strategy.properties.HystrixProperty; + +/** + * @author daniellavoie + */ +public class SecurityContextConcurrencyStrategy extends HystrixConcurrencyStrategy { + private HystrixConcurrencyStrategy existingConcurrencyStrategy; + + public SecurityContextConcurrencyStrategy( + HystrixConcurrencyStrategy existingConcurrencyStrategy) { + this.existingConcurrencyStrategy = existingConcurrencyStrategy; + } + + @Override + public BlockingQueue getBlockingQueue(int maxQueueSize) { + return existingConcurrencyStrategy != null + ? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize) + : super.getBlockingQueue(maxQueueSize); + } + + @Override + public HystrixRequestVariable getRequestVariable( + HystrixRequestVariableLifecycle rv) { + return existingConcurrencyStrategy != null + ? existingConcurrencyStrategy.getRequestVariable(rv) + : super.getRequestVariable(rv); + } + + @Override + public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey, + HystrixProperty corePoolSize, + HystrixProperty maximumPoolSize, + HystrixProperty keepAliveTime, TimeUnit unit, + BlockingQueue workQueue) { + return existingConcurrencyStrategy != null + ? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize, + maximumPoolSize, keepAliveTime, unit, workQueue) + : super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize, + keepAliveTime, unit, workQueue); + } + + @Override + public Callable wrapCallable(Callable callable) { + return existingConcurrencyStrategy != null + ? existingConcurrencyStrategy + .wrapCallable(new DelegatingSecurityContextCallable(callable)) + : super.wrapCallable(new DelegatingSecurityContextCallable(callable)); + } +} diff --git a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java index debf0d10..0d49e12b 100644 --- a/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java +++ b/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/zuul/filters/ZuulProperties.java @@ -48,7 +48,7 @@ public class ZuulProperties { * duplicated if the proxy and the backend are secured with Spring. By default they * are added to the ignored headers if Spring Security is present. */ - private static final List SECURITY_HEADERS = Arrays.asList("Pragma", + public static final List SECURITY_HEADERS = Arrays.asList("Pragma", "Cache-Control", "X-Frame-Options", "X-Content-Type-Options", "X-XSS-Protection", "Expires"); diff --git a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories index 709e2987..09988567 100644 --- a/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories +++ b/spring-cloud-netflix-core/src/main/resources/META-INF/spring.factories @@ -5,6 +5,7 @@ org.springframework.cloud.netflix.feign.FeignAutoConfiguration,\ org.springframework.cloud.netflix.feign.encoding.FeignAcceptGzipEncodingAutoConfiguration,\ org.springframework.cloud.netflix.feign.encoding.FeignContentGzipEncodingAutoConfiguration,\ org.springframework.cloud.netflix.hystrix.HystrixAutoConfiguration,\ +org.springframework.cloud.netflix.hystrix.security.HystrixSecurityAutoConfiguration,\ org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration,\ org.springframework.cloud.netflix.rx.RxJavaAutoConfiguration,\ org.springframework.cloud.netflix.metrics.servo.ServoMetricsAutoConfiguration diff --git a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/HystrixOnlyTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/HystrixOnlyTests.java index 1e3478d5..799c8ac0 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/HystrixOnlyTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/HystrixOnlyTests.java @@ -16,6 +16,11 @@ package org.springframework.cloud.netflix.hystrix; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Base64; import java.util.Map; import org.junit.Test; @@ -30,6 +35,9 @@ import org.springframework.boot.test.web.client.TestRestTemplate; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +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.junit4.SpringJUnit4ClassRunner; import org.springframework.web.bind.annotation.RequestMapping; @@ -37,10 +45,6 @@ import org.springframework.web.bind.annotation.RestController; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - /** * @author Spencer Gibb */ @@ -51,6 +55,12 @@ public class HystrixOnlyTests { @Value("${local.server.port}") private int port; + + @Value("${security.user.username}") + private String username; + + @Value("${security.user.password}") + private String password; @Test public void testNormalExecution() { @@ -82,9 +92,27 @@ public class HystrixOnlyTests { map.containsKey("discovery")); } + + private Map getHealth() { - return new TestRestTemplate().getForObject("http://localhost:" + this.port - + "/admin/health", Map.class); + return new TestRestTemplate().exchange( + "http://localhost:" + this.port + "/admin/health", HttpMethod.GET, + new HttpEntity(createBasicAuthHeader(username, password)), + Map.class).getBody(); + } + + 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-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityApplication.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityApplication.java new file mode 100644 index 00000000..7d146703 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/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.netflix.hystrix.security; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.netflix.feign.EnableFeignClients; +import org.springframework.cloud.netflix.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-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityTests.java new file mode 100644 index 00000000..6645a905 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/HystrixSecurityTests.java @@ -0,0 +1,87 @@ +/* + * 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.netflix.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.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.cloud.netflix.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.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.RestTemplate; + +/** + * 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(SpringJUnit4ClassRunner.class) +@DirtiesContext +@SpringBootTest(classes = HystrixSecurityApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT, properties = "username.ribbon.listOfServers=localhost:${local.server.port}") +public class HystrixSecurityTests { + @Autowired + private CustomConcurrenyStrategy customConcurrenyStrategy; + + @Value("${local.server.port}") + private String serverPort; + + @Value("${security.user.username}") + private String username; + + @Value("${security.user.password}") + private String password; + + @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-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/CustomConcurrenyStrategy.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/CustomConcurrenyStrategy.java new file mode 100644 index 00000000..674adf67 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/CustomConcurrenyStrategy.java @@ -0,0 +1,23 @@ +package org.springframework.cloud.netflix.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-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/ProxyUsernameController.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/ProxyUsernameController.java new file mode 100644 index 00000000..2f30427c --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/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.netflix.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-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/TestInterceptor.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/TestInterceptor.java new file mode 100644 index 00000000..c904de7e --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/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.netflix.hystrix.security.app; + +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +import feign.RequestInterceptor; +import feign.RequestTemplate; + +/** + * 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-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/UsernameClient.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/UsernameClient.java new file mode 100644 index 00000000..b630f839 --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/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.netflix.hystrix.security.app; + +import org.springframework.cloud.netflix.feign.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-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/UsernameController.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/hystrix/security/app/UsernameController.java new file mode 100644 index 00000000..54605b8e --- /dev/null +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/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.netflix.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-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ZuulPropertiesTests.java b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ZuulPropertiesTests.java index 5a1270bb..60e25406 100644 --- a/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ZuulPropertiesTests.java +++ b/spring-cloud-netflix-core/src/test/java/org/springframework/cloud/netflix/zuul/filters/ZuulPropertiesTests.java @@ -46,7 +46,8 @@ public class ZuulPropertiesTests { @Test public void defaultIgnoredHeaders() { - assertTrue(this.zuul.getIgnoredHeaders().isEmpty()); + assertTrue(this.zuul.getIgnoredHeaders() + .containsAll(ZuulProperties.SECURITY_HEADERS)); } @Test @@ -60,8 +61,8 @@ public class ZuulPropertiesTests { ZuulRoute route = new ZuulRoute("foo"); this.zuul.getRoutes().put("foo", route); assertTrue(this.zuul.getRoutes().get("foo").getSensitiveHeaders().isEmpty()); - assertTrue(this.zuul.getSensitiveHeaders().containsAll( - Arrays.asList("Cookie", "Set-Cookie", "Authorization"))); + assertTrue(this.zuul.getSensitiveHeaders() + .containsAll(Arrays.asList("Cookie", "Set-Cookie", "Authorization"))); } @Test diff --git a/spring-cloud-netflix-core/src/test/resources/application.yml b/spring-cloud-netflix-core/src/test/resources/application.yml index 777e546f..dd7d5dea 100644 --- a/spring-cloud-netflix-core/src/test/resources/application.yml +++ b/spring-cloud-netflix-core/src/test/resources/application.yml @@ -53,7 +53,15 @@ zuul: stores: url: http://localhost:8081 path: /stores/** +hystrix: + shareSecurityContext: true feignClient: localappName: localapp methodLevelRequestMappingPath: /hello2 myPlaceholderHeader: myPlaceholderHeaderValue +security: + basic: + path: /proxy-username + user: + username: user + password: password \ No newline at end of file