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