Merge branch '2.0.x'

This commit is contained in:
Spencer Gibb
2018-12-10 15:41:21 -05:00
7 changed files with 196 additions and 2 deletions

26
docs/src/main/asciidoc/spring-cloud-netflix.adoc Normal file → Executable file
View File

@@ -335,6 +335,16 @@ This section describes how to set up a Eureka server.
To include Eureka Server in your project, use the starter with a group ID of `org.springframework.cloud` and an artifact ID of `spring-cloud-starter-netflix-eureka-server`.
See the http://projects.spring.io/spring-cloud/[Spring Cloud Project page] for details on setting up your build system with the current Spring Cloud Release Train.
NOTE: If your project already uses Thymeleaf as its template engine, the Freemarker templates of the Eureka server may not be loaded correctly. In this case it is necessary to configure the template loader manually:
.application.yml
----
spring:
freemarker:
template-loader-path: classpath:/templates/
prefer-file-system-access: false
----
[[spring-cloud-running-eureka-server]]
=== How to Run a Eureka Server
@@ -1505,6 +1515,22 @@ NOTE: This special flag works only with `SimpleHostRoutingFilter`. Also, you loo
query parameters with `RequestContext.getCurrentContext().setRequestQueryParams(someOverriddenParameters)`, because
the query string is now fetched directly on the original `HttpServletRequest`.
=== Request URI Encoding
When processing the incoming request, request URI is decoded before matching them to routes.
The request URI is then re-encoded when the back end request is rebuilt in the route filters.
This can cause some unexpected behavior if your URI includes the encoded "/" character.
To use the original request URI, it is possible to pass a special flag to 'ZuulProperties' so that the URI will be taken as is with the `HttpServletRequest::getRequestURI` method, as shown in the following example:
.application.yml
[source,yaml]
----
zuul:
decodeUrl: false
----
NOTE: If you are overriding request URI using `requestURI` RequestContext attribute and this flag is set to false, then the URL set in the request context will not be encoded. It will be your responsibility to make sure the URL is already encoded.
=== Plain Embedded Zuul
If you use `@EnableZuulServer` (instead of `@EnableZuulProxy`), you can also run a Zuul server without proxying or selectively switch on parts of the proxying platform.

View File

@@ -0,0 +1,110 @@
/*
* Copyright 2013-2018 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 com.netflix.hystrix.strategy.HystrixPlugins;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategy;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
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;
import org.junit.Test;
import org.mockito.internal.util.reflection.FieldSetter;
import java.lang.reflect.Field;
import static org.junit.Assert.assertEquals;
/**
* @author : ailin.zhou
*/
public class HystrixSecurityAutoConfigurationTest {
@Test
public void testInit() throws NoSuchFieldException, IllegalAccessException {
//save test context
HystrixEventNotifier eventNotifier = HystrixPlugins.getInstance()
.getEventNotifier();
HystrixMetricsPublisher metricsPublisher = HystrixPlugins.getInstance()
.getMetricsPublisher();
HystrixPropertiesStrategy propertiesStrategy = HystrixPlugins.getInstance()
.getPropertiesStrategy();
HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance()
.getCommandExecutionHook();
HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
//test
testForMultiConcurrentStrategy();
//recover test context
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(concurrencyStrategy);
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
}
private void testForMultiConcurrentStrategy() throws IllegalAccessException, NoSuchFieldException {
HystrixSecurityAutoConfiguration securityStrategy = new HystrixSecurityAutoConfiguration();
//1.existingConcurrencyStrategy is null, registeredStrategy is default
HystrixPlugins.reset();
securityStrategy.init();
//result is default
assertEquals(HystrixConcurrencyStrategyDefault.getInstance(), getOriginalInSecurityConcurrencyStrategy());
//2.existingConcurrencyStrategy is null, registered strategy is customized
HystrixPlugins.reset();
HystrixConcurrencyStrategy customized = new HystrixConcurrencyStrategy() {
};
HystrixPlugins.getInstance().registerConcurrencyStrategy(customized);
securityStrategy.init();
//result is customized
assertEquals(customized, getOriginalInSecurityConcurrencyStrategy());
//3.existingConcurrencyStrategy is not null, registeredStrategy is default.
HystrixPlugins.reset();
HystrixConcurrencyStrategy existingConcurrencyStrategy = new HystrixConcurrencyStrategy() {
};
FieldSetter.setField(securityStrategy, securityStrategy.getClass().getDeclaredField("existingConcurrencyStrategy"), existingConcurrencyStrategy);
securityStrategy.init();
//result is existingConcurrencyStrategy
assertEquals(existingConcurrencyStrategy, getOriginalInSecurityConcurrencyStrategy());
//4.existingConcurrencyStrategy is not null, registeredStrategy is customized.
HystrixPlugins.reset();
HystrixPlugins.getInstance().registerConcurrencyStrategy(customized);
FieldSetter.setField(securityStrategy, securityStrategy.getClass().getDeclaredField("existingConcurrencyStrategy"), existingConcurrencyStrategy);
securityStrategy.init();
assertEquals(existingConcurrencyStrategy, getOriginalInSecurityConcurrencyStrategy());
}
private HystrixConcurrencyStrategy getOriginalInSecurityConcurrencyStrategy() throws IllegalAccessException, NoSuchFieldException {
HystrixConcurrencyStrategy concurrencyStrategy = HystrixPlugins.getInstance().getConcurrencyStrategy();
Field existingConcurrencyStrategy = concurrencyStrategy.getClass().getDeclaredField("existingConcurrencyStrategy");
existingConcurrencyStrategy.setAccessible(true);
HystrixConcurrencyStrategy strategyInSecurityStrategy = (HystrixConcurrencyStrategy) existingConcurrencyStrategy.get(concurrencyStrategy);
return strategyInSecurityStrategy;
}
}

View File

@@ -18,6 +18,9 @@ package org.springframework.cloud.netflix.hystrix.security;
import javax.annotation.PostConstruct;
import com.netflix.hystrix.strategy.concurrency.HystrixConcurrencyStrategyDefault;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.AllNestedConditions;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -42,6 +45,7 @@ import com.netflix.hystrix.strategy.properties.HystrixPropertiesStrategy;
@Conditional(HystrixSecurityCondition.class)
@ConditionalOnClass({ Hystrix.class, SecurityContext.class })
public class HystrixSecurityAutoConfiguration {
private static final Log LOGGER = LogFactory.getLog(HystrixSecurityAutoConfiguration.class);
@Autowired(required = false)
private HystrixConcurrencyStrategy existingConcurrencyStrategy;
@@ -56,18 +60,36 @@ public class HystrixSecurityAutoConfiguration {
.getPropertiesStrategy();
HystrixCommandExecutionHook commandExecutionHook = HystrixPlugins.getInstance()
.getCommandExecutionHook();
HystrixConcurrencyStrategy concurrencyStrategy = detectRegisteredConcurrencyStrategy();
HystrixPlugins.reset();
// Registers existing plugins excepts the Concurrent Strategy plugin.
HystrixPlugins.getInstance().registerConcurrencyStrategy(
new SecurityContextConcurrencyStrategy(existingConcurrencyStrategy));
new SecurityContextConcurrencyStrategy(concurrencyStrategy));
HystrixPlugins.getInstance().registerEventNotifier(eventNotifier);
HystrixPlugins.getInstance().registerMetricsPublisher(metricsPublisher);
HystrixPlugins.getInstance().registerPropertiesStrategy(propertiesStrategy);
HystrixPlugins.getInstance().registerCommandExecutionHook(commandExecutionHook);
}
private HystrixConcurrencyStrategy detectRegisteredConcurrencyStrategy() {
HystrixConcurrencyStrategy registeredStrategy = HystrixPlugins.getInstance()
.getConcurrencyStrategy();
if (existingConcurrencyStrategy == null) {
return registeredStrategy;
}
//Hystrix registered a default Strategy.
if (registeredStrategy instanceof HystrixConcurrencyStrategyDefault){
return existingConcurrencyStrategy;
}
//If registeredStrategy not the default and not some use bean of existingConcurrencyStrategy.
if (!existingConcurrencyStrategy.equals(registeredStrategy)){
LOGGER.warn("Multiple HystrixConcurrencyStrategy detected. Bean of HystrixConcurrencyStrategy was used.");
}
return existingConcurrencyStrategy;
}
static class HystrixSecurityCondition extends AllNestedConditions {
public HystrixSecurityCondition() {

View File

@@ -78,6 +78,8 @@ public class ProxyRequestHelper {
private boolean addHostHeader = false;
private boolean urlDecoded = true;
@Deprecated
//TODO Remove in 2.1.x
public ProxyRequestHelper() {}
@@ -86,6 +88,7 @@ public class ProxyRequestHelper {
this.ignoredHeaders.addAll(zuulProperties.getIgnoredHeaders());
this.traceRequestBody = zuulProperties.isTraceRequestBody();
this.addHostHeader = zuulProperties.isAddHostHeader();
this.urlDecoded = zuulProperties.isDecodeUrl();
}
public void setWhitelistHosts(Set<String> whitelistHosts) {
@@ -114,7 +117,10 @@ public class ProxyRequestHelper {
String contextURI = (String) context.get(REQUEST_URI_KEY);
if (contextURI != null) {
try {
uri = UriUtils.encodePath(contextURI, characterEncoding(request));
uri = contextURI;
if (this.urlDecoded) {
uri = UriUtils.encodePath(contextURI, characterEncoding(request));
}
}
catch (Exception e) {
log.debug(

View File

@@ -140,6 +140,11 @@ public class ZuulProperties {
*/
private boolean removeSemicolonContent = true;
/**
* Flag to indicate whether to decode the matched URL or use it as is.
*/
private boolean decodeUrl = true;
/**
* List of sensitive headers that are not passed to downstream requests. Defaults to a
* "safe" set of headers that commonly contain user credentials. It's OK to remove
@@ -764,6 +769,14 @@ public class ZuulProperties {
this.removeSemicolonContent = removeSemicolonContent;
}
public boolean isDecodeUrl() {
return decodeUrl;
}
public void setDecodeUrl(boolean decodeUrl) {
this.decodeUrl = decodeUrl;
}
public Set<String> getSensitiveHeaders() {
return sensitiveHeaders;
}

View File

@@ -88,6 +88,7 @@ public class PreDecorationFilter extends ZuulFilter {
this.routeLocator = routeLocator;
this.properties = properties;
this.urlPathHelper.setRemoveSemicolonContent(properties.isRemoveSemicolonContent());
this.urlPathHelper.setUrlDecode(properties.isDecodeUrl());
this.dispatcherServletPath = dispatcherServletPath;
this.proxyRequestHelper = proxyRequestHelper;
}

View File

@@ -330,6 +330,22 @@ public class PreDecorationFilterTests {
getHeader(ctx.getOriginResponseHeaders(), "x-zuul-serviceid"));
}
@Test
public void dontDecodeUrl() {
this.properties.setPrefix("/api");
this.properties.setStripPrefix(true);
this.properties.setDecodeUrl(false);
this.request.setRequestURI("/api/foo/encoded%2Fpath");
this.request.setContextPath("/context-path");
this.routeLocator.addRoute(
new ZuulRoute("foo", "/foo/**", "foo", null, false, null, null));
this.filter = new PreDecorationFilter(this.routeLocator, "/", this.properties,
this.proxyRequestHelper);
this.filter.run();
RequestContext ctx = RequestContext.getCurrentContext();
assertEquals("/foo/encoded%2Fpath", ctx.get(REQUEST_URI_KEY));
}
@Test
public void routeIgnoreContextPathIfPrefixHeader() {
this.properties.setStripPrefix(false);