Merge branch 'master' into zuul_hystrix_threadpool
This commit is contained in:
@@ -59,6 +59,11 @@ public @interface FeignClient {
|
||||
*/
|
||||
@AliasFor("value")
|
||||
String name() default "";
|
||||
|
||||
/**
|
||||
* Sets the <code>@Qualifier</code> value for the feign client.
|
||||
*/
|
||||
String qualifier() default "";
|
||||
|
||||
/**
|
||||
* An absolute URL or resolvable hostname (the protocol is optional).
|
||||
|
||||
@@ -184,6 +184,12 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
String alias = name + "FeignClient";
|
||||
AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();
|
||||
beanDefinition.setPrimary(true);
|
||||
|
||||
String qualifier = getQualifier(attributes);
|
||||
if (StringUtils.hasText(qualifier)) {
|
||||
alias = qualifier;
|
||||
}
|
||||
|
||||
BeanDefinitionHolder holder = new BeanDefinitionHolder(beanDefinition, className,
|
||||
new String[] { alias });
|
||||
BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);
|
||||
@@ -317,6 +323,17 @@ class FeignClientsRegistrar implements ImportBeanDefinitionRegistrar,
|
||||
}
|
||||
return basePackages;
|
||||
}
|
||||
|
||||
private String getQualifier(Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
return null;
|
||||
}
|
||||
String qualifier = (String) client.get("qualifier");
|
||||
if (StringUtils.hasText(qualifier)) {
|
||||
return qualifier;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getClientName(Map<String, Object> client) {
|
||||
if (client == null) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Runnable> getBlockingQueue(int maxQueueSize) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy.getBlockingQueue(maxQueueSize)
|
||||
: super.getBlockingQueue(maxQueueSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> HystrixRequestVariable<T> getRequestVariable(
|
||||
HystrixRequestVariableLifecycle<T> rv) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy.getRequestVariable(rv)
|
||||
: super.getRequestVariable(rv);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadPoolExecutor getThreadPool(HystrixThreadPoolKey threadPoolKey,
|
||||
HystrixProperty<Integer> corePoolSize,
|
||||
HystrixProperty<Integer> maximumPoolSize,
|
||||
HystrixProperty<Integer> keepAliveTime, TimeUnit unit,
|
||||
BlockingQueue<Runnable> workQueue) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy.getThreadPool(threadPoolKey, corePoolSize,
|
||||
maximumPoolSize, keepAliveTime, unit, workQueue)
|
||||
: super.getThreadPool(threadPoolKey, corePoolSize, maximumPoolSize,
|
||||
keepAliveTime, unit, workQueue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Callable<T> wrapCallable(Callable<T> callable) {
|
||||
return existingConcurrencyStrategy != null
|
||||
? existingConcurrencyStrategy
|
||||
.wrapCallable(new DelegatingSecurityContextCallable<T>(callable))
|
||||
: super.wrapCallable(new DelegatingSecurityContextCallable<T>(callable));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
package org.springframework.cloud.netflix.metrics;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
@@ -25,6 +27,7 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
|
||||
@@ -80,8 +83,8 @@ public class MetricsInterceptorConfiguration {
|
||||
return new MetricsInterceptorPostProcessor();
|
||||
}
|
||||
|
||||
private static class MetricsInterceptorPostProcessor implements
|
||||
BeanPostProcessor, ApplicationContextAware {
|
||||
private static class MetricsInterceptorPostProcessor
|
||||
implements BeanPostProcessor, ApplicationContextAware {
|
||||
private ApplicationContext context;
|
||||
private MetricsClientHttpRequestInterceptor interceptor;
|
||||
|
||||
@@ -97,7 +100,12 @@ public class MetricsInterceptorConfiguration {
|
||||
this.interceptor = this.context
|
||||
.getBean(MetricsClientHttpRequestInterceptor.class);
|
||||
}
|
||||
((RestTemplate) bean).getInterceptors().add(interceptor);
|
||||
RestTemplate restTemplate = (RestTemplate) bean;
|
||||
// create a new list as the old one may be unmodifiable (ie Arrays.asList())
|
||||
ArrayList<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
|
||||
interceptors.add(interceptor);
|
||||
interceptors.addAll(restTemplate.getInterceptors());
|
||||
restTemplate.setInterceptors(interceptors);
|
||||
}
|
||||
return bean;
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ public class RibbonClientConfiguration {
|
||||
public URI reconstructURIWithServer(Server server, URI original) {
|
||||
String scheme = original.getScheme();
|
||||
if (!"https".equals(scheme) && this.serverIntrospector.isSecure(server)) {
|
||||
original = UriComponentsBuilder.fromUri(original).scheme("https").build()
|
||||
original = UriComponentsBuilder.fromUri(original).scheme("https").build(true)
|
||||
.toUri();
|
||||
}
|
||||
return super.reconstructURIWithServer(server, original);
|
||||
|
||||
@@ -52,7 +52,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<String> SECURITY_HEADERS = Arrays.asList("Pragma",
|
||||
public static final List<String> SECURITY_HEADERS = Arrays.asList("Pragma",
|
||||
"Cache-Control", "X-Frame-Options", "X-Content-Type-Options",
|
||||
"X-XSS-Protection", "Expires");
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -26,6 +26,7 @@ import java.util.Map;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
@@ -65,6 +66,7 @@ public class FeignClientTests {
|
||||
@Autowired
|
||||
private ApplicationContext context;
|
||||
|
||||
@Qualifier("uniquequalifier")
|
||||
@Autowired
|
||||
private org.springframework.cloud.netflix.feign.beans.extra.TestClient extraClient;
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ import org.springframework.cloud.netflix.feign.beans.FeignClientTests.Hello;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
@FeignClient(value = "otherapp")
|
||||
@FeignClient(value = "otherapp", qualifier = "uniquequalifier")
|
||||
public interface TestClient {
|
||||
@RequestMapping(method = RequestMethod.GET, value = "/hello")
|
||||
Hello getHello();
|
||||
|
||||
@@ -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<Void>(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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
@@ -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<Void>(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);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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 <T> Callable<T> wrapCallable(Callable<T> callable) {
|
||||
this.hookCalled = true;
|
||||
|
||||
return super.wrapCallable(callable);
|
||||
}
|
||||
|
||||
public boolean isHookCalled() {
|
||||
return hookCalled;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,11 @@ package org.springframework.cloud.netflix.metrics;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.PropertyPlaceholderAutoConfiguration;
|
||||
@@ -28,6 +31,7 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.client.ClientHttpRequestInterceptor;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.TestPropertySource;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
@@ -58,6 +62,9 @@ public class MetricsClientHttpRequestInterceptorTests {
|
||||
@Autowired
|
||||
RestTemplate restTemplate;
|
||||
|
||||
@Autowired
|
||||
RestTemplate restTemplateWithFakeInterceptorsList;
|
||||
|
||||
@Test
|
||||
public void metricsGatheredWhenSuccessful() {
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplate);
|
||||
@@ -79,6 +86,28 @@ public class MetricsClientHttpRequestInterceptorTests {
|
||||
|
||||
mockServer.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restTemplateWithFakeInterceptorList() {
|
||||
MockRestServiceServer mockServer = MockRestServiceServer.createServer(restTemplateWithFakeInterceptorsList);
|
||||
mockServer.expect(MockRestRequestMatchers.requestTo("/test/123"))
|
||||
.andExpect(MockRestRequestMatchers.method(HttpMethod.GET))
|
||||
.andRespond(MockRestResponseCreators.withSuccess("OK", MediaType.APPLICATION_JSON));
|
||||
String s = restTemplate.getForObject("/test/{id}", String.class, 123);
|
||||
|
||||
MonitorConfig.Builder builder = new MonitorConfig.Builder("metricName")
|
||||
.withTag("method", "GET")
|
||||
.withTag("uri", "_test_-id-")
|
||||
.withTag("status", "200")
|
||||
.withTag("clientName", "none");
|
||||
|
||||
BasicTimer timer = servoMonitorCache.getTimer(builder.build());
|
||||
|
||||
assertEquals(2L, (long) timer.getCount());
|
||||
assertEquals("OK", s);
|
||||
|
||||
mockServer.verify();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@@ -95,7 +124,15 @@ class MetricsRestTemplateTestConfig {
|
||||
@Configuration
|
||||
class MetricsRestTemplateRestTemplateConfig {
|
||||
@Bean
|
||||
@Primary
|
||||
RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
|
||||
@Bean(name="restTemplateWithFakeInterceptorsList")
|
||||
RestTemplate restTemplateWithFakeInterceptorsList() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
restTemplate.setInterceptors(Arrays.asList(Mockito.mock(ClientHttpRequestInterceptor.class)));
|
||||
return restTemplate;
|
||||
}
|
||||
}
|
||||
@@ -16,26 +16,46 @@
|
||||
|
||||
package org.springframework.cloud.netflix.ribbon;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import java.net.URI;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.springframework.cloud.netflix.ribbon.RibbonClientConfiguration.OverrideRestClient;
|
||||
|
||||
import com.netflix.client.config.CommonClientConfigKey;
|
||||
import com.netflix.client.config.DefaultClientConfigImpl;
|
||||
import com.netflix.client.config.IClientConfig;
|
||||
import org.junit.Test;
|
||||
import com.netflix.loadbalancer.Server;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* @author Spencer Gibb
|
||||
*/
|
||||
public class RibbonClientConfigurationTests {
|
||||
|
||||
@Test
|
||||
public void restClientInitCalledOnce() {
|
||||
CountingConfig config = new CountingConfig();
|
||||
private CountingConfig config;
|
||||
|
||||
@Mock
|
||||
private ServerIntrospector inspector;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
config = new CountingConfig();
|
||||
config.setProperty(CommonClientConfigKey.ConnectTimeout, "1");
|
||||
config.setProperty(CommonClientConfigKey.ReadTimeout, "1");
|
||||
config.setProperty(CommonClientConfigKey.MaxHttpConnectionsPerHost, "1");
|
||||
config.setClientName("testClient");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void restClientInitCalledOnce() {
|
||||
new TestRestClient(config);
|
||||
assertThat(config.count, is(equalTo(1)));
|
||||
}
|
||||
@@ -44,7 +64,43 @@ public class RibbonClientConfigurationTests {
|
||||
int count = 0;
|
||||
}
|
||||
|
||||
static class TestRestClient extends RibbonClientConfiguration.OverrideRestClient {
|
||||
@Test
|
||||
public void testSecureUriFromClientConfig() throws Exception {
|
||||
Server server = new Server("foo", 7777);
|
||||
when(inspector.isSecure(server)).thenReturn(true);
|
||||
|
||||
OverrideRestClient overrideRestClient = new OverrideRestClient(this.config,
|
||||
inspector);
|
||||
URI uri = overrideRestClient.reconstructURIWithServer(server,
|
||||
new URI("http://foo/"));
|
||||
assertThat(uri, is(new URI("https://foo:7777/")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInSecureUriFromClientConfig() throws Exception {
|
||||
Server server = new Server("foo", 7777);
|
||||
when(inspector.isSecure(server)).thenReturn(false);
|
||||
|
||||
OverrideRestClient overrideRestClient = new OverrideRestClient(this.config,
|
||||
inspector);
|
||||
URI uri = overrideRestClient.reconstructURIWithServer(server,
|
||||
new URI("http://foo/"));
|
||||
assertThat(uri, is(new URI("http://foo:7777/")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotDoubleEncodedWhenSecure() throws Exception {
|
||||
Server server = new Server("foo", 7777);
|
||||
when(inspector.isSecure(server)).thenReturn(true);
|
||||
|
||||
OverrideRestClient overrideRestClient = new OverrideRestClient(this.config,
|
||||
inspector);
|
||||
URI uri = overrideRestClient.reconstructURIWithServer(server,
|
||||
new URI("http://foo/%20bar"));
|
||||
assertThat(uri, is(new URI("https://foo:7777/%20bar")));
|
||||
}
|
||||
|
||||
static class TestRestClient extends OverrideRestClient {
|
||||
|
||||
private TestRestClient(IClientConfig ncc) {
|
||||
super(ncc, new DefaultServerIntrospector());
|
||||
|
||||
@@ -23,7 +23,6 @@ import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -91,7 +90,6 @@ public class FormZuulProxyApplicationTests {
|
||||
assertEquals("Posted! {foo=[bar]}", result.getBody());
|
||||
}
|
||||
|
||||
@Ignore // TODO: fix this test
|
||||
@Test
|
||||
public void postWithMultipartForm() {
|
||||
MultiValueMap<String, String> form = new LinkedMultiValueMap<String, String>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user