From b2df97d0c467d246a56870d63fc637087eec0ef8 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Thu, 3 Feb 2022 12:31:58 -0500 Subject: [PATCH 1/9] Moves configuration of HttpClient. Moves configuration of HttpClient out of GatewayAutoConfiguration and into HttpClientFactory. This allows customization by the user and allows tests to see what has been configured on the HttpClient. When HTTP2 is enabled, the insecure trustmanager factory option must be set explicitly. --- .../config/GatewayAutoConfiguration.java | 153 +-------- .../gateway/config/HttpClientFactory.java | 314 ++++++++++++++++++ .../gateway/config/HttpClientProperties.java | 3 + .../config/GatewayAutoConfigurationTests.java | 180 ++++++++-- 4 files changed, 471 insertions(+), 179 deletions(-) create mode 100644 spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 0254ebda..e4b3e601 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -16,26 +16,16 @@ package org.springframework.cloud.gateway.config; -import java.security.cert.X509Certificate; -import java.time.Duration; import java.util.List; import java.util.Set; import java.util.function.Supplier; -import io.netty.channel.ChannelOption; -import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import reactor.core.publisher.Flux; -import reactor.netty.http.Http11SslContextSpec; -import reactor.netty.http.Http2SslContextSpec; -import reactor.netty.http.HttpProtocol; import reactor.netty.http.client.HttpClient; import reactor.netty.http.client.WebsocketClientSpec; import reactor.netty.http.server.WebsocketServerSpec; -import reactor.netty.resources.ConnectionProvider; -import reactor.netty.tcp.SslProvider.ProtocolSslContextSpec; -import reactor.netty.transport.ProxyProvider; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.ObjectProvider; @@ -154,15 +144,12 @@ import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Primary; -import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.convert.ConversionService; import org.springframework.core.env.Environment; import org.springframework.http.codec.ServerCodecConfigurer; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; import org.springframework.security.web.server.SecurityWebFilterChain; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.springframework.validation.Validator; import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; @@ -172,9 +159,6 @@ import org.springframework.web.reactive.socket.server.WebSocketService; import org.springframework.web.reactive.socket.server.support.HandshakeWebSocketService; import org.springframework.web.reactive.socket.server.upgrade.ReactorNettyRequestUpgradeStrategy; -import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED; -import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED; - /** * @author Spencer Gibb * @author Ziemowit Stolarczyk @@ -665,139 +649,10 @@ public class GatewayAutoConfiguration { } @Bean - @ConditionalOnMissingBean - public HttpClient gatewayHttpClient(HttpClientProperties properties, ServerProperties serverProperties, - List customizers) { - - // configure pool resources - ConnectionProvider connectionProvider = buildConnectionProvider(properties); - - HttpClient httpClient = HttpClient.create(connectionProvider) - // TODO: move customizations to HttpClientCustomizers - .httpResponseDecoder(spec -> { - if (properties.getMaxHeaderSize() != null) { - // cast to int is ok, since @Max is Integer.MAX_VALUE - spec.maxHeaderSize((int) properties.getMaxHeaderSize().toBytes()); - } - if (properties.getMaxInitialLineLength() != null) { - // cast to int is ok, since @Max is Integer.MAX_VALUE - spec.maxInitialLineLength((int) properties.getMaxInitialLineLength().toBytes()); - } - return spec; - }); - - if (serverProperties.getHttp2().isEnabled()) { - httpClient = httpClient.protocol(HttpProtocol.HTTP11, HttpProtocol.H2); - } - - if (properties.getConnectTimeout() != null) { - httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout()); - } - - // configure proxy if proxy host is set. - if (StringUtils.hasText(properties.getProxy().getHost())) { - HttpClientProperties.Proxy proxy = properties.getProxy(); - - httpClient = httpClient.proxy(proxySpec -> { - ProxyProvider.Builder builder = proxySpec.type(proxy.getType()).host(proxy.getHost()); - - PropertyMapper map = PropertyMapper.get(); - - map.from(proxy::getPort).whenNonNull().to(builder::port); - map.from(proxy::getUsername).whenHasText().to(builder::username); - map.from(proxy::getPassword).whenHasText().to(password -> builder.password(s -> password)); - map.from(proxy::getNonProxyHostsPattern).whenHasText().to(builder::nonProxyHosts); - }); - } - - HttpClientProperties.Ssl ssl = properties.getSsl(); - if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) - || ssl.getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) { - httpClient = httpClient.secure(sslContextSpec -> { - // configure ssl - ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled()) - ? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient(); - clientSslContext.configure(sslContextBuilder -> { - X509Certificate[] trustedX509Certificates = ssl.getTrustedX509CertificatesForTrustManager(); - if (trustedX509Certificates.length > 0) { - sslContextBuilder.trustManager(trustedX509Certificates); - } - else if (ssl.isUseInsecureTrustManager()) { - sslContextBuilder.trustManager(InsecureTrustManagerFactory.INSTANCE); - } - - try { - sslContextBuilder.keyManager(ssl.getKeyManagerFactory()); - } - catch (Exception e) { - logger.error(e); - } - }); - - sslContextSpec.sslContext(clientSslContext).handshakeTimeout(ssl.getHandshakeTimeout()) - .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()) - .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout()); - }); - } - else if (serverProperties.getHttp2().isEnabled()) { - httpClient = httpClient.secure(sslContextSpec -> { - Http2SslContextSpec clientSslCtxt = Http2SslContextSpec.forClient() - .configure(builder -> builder.trustManager(InsecureTrustManagerFactory.INSTANCE)); - sslContextSpec.sslContext(clientSslCtxt).handshakeTimeout(ssl.getHandshakeTimeout()) - .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()) - .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout()); - }); - } - - if (properties.isWiretap()) { - httpClient = httpClient.wiretap(true); - } - - if (properties.isCompression()) { - httpClient = httpClient.compress(true); - } - - if (!CollectionUtils.isEmpty(customizers)) { - customizers.sort(AnnotationAwareOrderComparator.INSTANCE); - for (HttpClientCustomizer customizer : customizers) { - httpClient = customizer.customize(httpClient); - } - } - - return httpClient; - } - - private ConnectionProvider buildConnectionProvider(HttpClientProperties properties) { - HttpClientProperties.Pool pool = properties.getPool(); - - ConnectionProvider connectionProvider; - if (pool.getType() == DISABLED) { - connectionProvider = ConnectionProvider.newConnection(); - } - else { - // create either Fixed or Elastic pool - ConnectionProvider.Builder builder = ConnectionProvider.builder(pool.getName()); - if (pool.getType() == FIXED) { - builder.maxConnections(pool.getMaxConnections()).pendingAcquireMaxCount(-1) - .pendingAcquireTimeout(Duration.ofMillis(pool.getAcquireTimeout())); - } - else { - // Elastic - builder.maxConnections(Integer.MAX_VALUE).pendingAcquireTimeout(Duration.ofMillis(0)) - .pendingAcquireMaxCount(-1); - } - - if (pool.getMaxIdleTime() != null) { - builder.maxIdleTime(pool.getMaxIdleTime()); - } - if (pool.getMaxLifeTime() != null) { - builder.maxLifeTime(pool.getMaxLifeTime()); - } - builder.evictInBackground(pool.getEvictionInterval()); - builder.metrics(pool.isMetrics()); - connectionProvider = builder.build(); - } - return connectionProvider; + @ConditionalOnMissingBean({ HttpClient.class, HttpClientFactory.class }) + public HttpClientFactory gatewayHttpClientFactory(HttpClientProperties properties, + ServerProperties serverProperties, List customizers) { + return new HttpClientFactory(properties, serverProperties, customizers); } @Bean diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java new file mode 100644 index 00000000..4d033750 --- /dev/null +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientFactory.java @@ -0,0 +1,314 @@ +/* + * Copyright 2013-2022 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 + * + * https://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.gateway.config; + +import java.io.IOException; +import java.net.URL; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchProviderException; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManagerFactory; + +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import reactor.netty.http.Http11SslContextSpec; +import reactor.netty.http.Http2SslContextSpec; +import reactor.netty.http.HttpProtocol; +import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpResponseDecoderSpec; +import reactor.netty.resources.ConnectionProvider; +import reactor.netty.tcp.SslProvider; +import reactor.netty.transport.ProxyProvider; + +import org.springframework.beans.factory.config.AbstractFactoryBean; +import org.springframework.boot.autoconfigure.web.ServerProperties; +import org.springframework.boot.context.properties.PropertyMapper; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.util.CollectionUtils; +import org.springframework.util.ResourceUtils; +import org.springframework.util.StringUtils; + +import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.DISABLED; +import static org.springframework.cloud.gateway.config.HttpClientProperties.Pool.PoolType.FIXED; + +/** + * Factory Bean that allows users to extend and customize parts of the HttpClient. Also + * allows for testing the configuration of the HttpClient. + * + * @author Spencer Gibb + * @since 3.1.1 + */ +public class HttpClientFactory extends AbstractFactoryBean { + + protected final HttpClientProperties properties; + + protected final ServerProperties serverProperties; + + protected final List customizers; + + public HttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties, + List customizers) { + this.properties = properties; + this.serverProperties = serverProperties; + this.customizers = customizers; + } + + @Override + public Class getObjectType() { + return HttpClient.class; + } + + @Override + protected HttpClient createInstance() { + // configure pool resources + ConnectionProvider connectionProvider = buildConnectionProvider(properties); + + HttpClient httpClient = HttpClient.create(connectionProvider) + // TODO: move customizations to HttpClientCustomizers + .httpResponseDecoder(this::httpResponseDecoder); + + if (serverProperties.getHttp2().isEnabled()) { + httpClient = httpClient.protocol(HttpProtocol.HTTP11, HttpProtocol.H2); + } + + if (properties.getConnectTimeout() != null) { + httpClient = httpClient.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, properties.getConnectTimeout()); + } + + httpClient = configureProxy(httpClient); + + httpClient = configureSsl(httpClient); + + if (properties.isWiretap()) { + httpClient = httpClient.wiretap(true); + } + + if (properties.isCompression()) { + httpClient = httpClient.compress(true); + } + + httpClient = applyCustomizers(httpClient); + + return httpClient; + } + + private HttpClient applyCustomizers(HttpClient httpClient) { + if (!CollectionUtils.isEmpty(customizers)) { + customizers.sort(AnnotationAwareOrderComparator.INSTANCE); + for (HttpClientCustomizer customizer : customizers) { + httpClient = customizer.customize(httpClient); + } + } + return httpClient; + } + + protected HttpClient configureSsl(HttpClient httpClient) { + HttpClientProperties.Ssl ssl = properties.getSsl(); + if ((ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) + || getTrustedX509CertificatesForTrustManager().length > 0 || ssl.isUseInsecureTrustManager()) { + httpClient = httpClient.secure(sslContextSpec -> { + // configure ssl + configureSslContext(ssl, sslContextSpec); + }); + } + return httpClient; + } + + protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) { + SslProvider.ProtocolSslContextSpec clientSslContext = (serverProperties.getHttp2().isEnabled()) + ? Http2SslContextSpec.forClient() : Http11SslContextSpec.forClient(); + clientSslContext.configure(sslContextBuilder -> { + X509Certificate[] trustedX509Certificates = getTrustedX509CertificatesForTrustManager(); + if (trustedX509Certificates.length > 0) { + setTrustManager(sslContextBuilder, trustedX509Certificates); + } + else if (ssl.isUseInsecureTrustManager()) { + setTrustManager(sslContextBuilder, InsecureTrustManagerFactory.INSTANCE); + } + + try { + sslContextBuilder.keyManager(getKeyManagerFactory()); + } + catch (Exception e) { + logger.error(e); + } + }); + + sslContextSpec.sslContext(clientSslContext).handshakeTimeout(ssl.getHandshakeTimeout()) + .closeNotifyFlushTimeout(ssl.getCloseNotifyFlushTimeout()) + .closeNotifyReadTimeout(ssl.getCloseNotifyReadTimeout()); + } + + protected HttpClient configureProxy(HttpClient httpClient) { + // configure proxy if proxy host is set. + if (StringUtils.hasText(properties.getProxy().getHost())) { + HttpClientProperties.Proxy proxy = properties.getProxy(); + + httpClient = httpClient.proxy(proxySpec -> { + configureProxyProvider(proxy, proxySpec); + }); + } + return httpClient; + } + + protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy, + ProxyProvider.TypeSpec proxySpec) { + ProxyProvider.Builder builder = proxySpec.type(proxy.getType()).host(proxy.getHost()); + + PropertyMapper map = PropertyMapper.get(); + + map.from(proxy::getPort).whenNonNull().to(builder::port); + map.from(proxy::getUsername).whenHasText().to(builder::username); + map.from(proxy::getPassword).whenHasText().to(password -> builder.password(s -> password)); + map.from(proxy::getNonProxyHostsPattern).whenHasText().to(builder::nonProxyHosts); + return builder; + } + + protected HttpResponseDecoderSpec httpResponseDecoder(HttpResponseDecoderSpec spec) { + if (properties.getMaxHeaderSize() != null) { + // cast to int is ok, since @Max is Integer.MAX_VALUE + spec.maxHeaderSize((int) properties.getMaxHeaderSize().toBytes()); + } + if (properties.getMaxInitialLineLength() != null) { + // cast to int is ok, since @Max is Integer.MAX_VALUE + spec.maxInitialLineLength((int) properties.getMaxInitialLineLength().toBytes()); + } + return spec; + } + + protected X509Certificate[] getTrustedX509CertificatesForTrustManager() { + HttpClientProperties.Ssl ssl = properties.getSsl(); + + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + ArrayList allCerts = new ArrayList<>(); + for (String trustedCert : ssl.getTrustedX509Certificates()) { + try { + URL url = ResourceUtils.getURL(trustedCert); + Collection certs = certificateFactory.generateCertificates(url.openStream()); + allCerts.addAll(certs); + } + catch (IOException e) { + throw new RuntimeException("Could not load certificate '" + trustedCert + "'", e); + } + } + return allCerts.toArray(new X509Certificate[allCerts.size()]); + } + catch (CertificateException e1) { + throw new RuntimeException("Could not load CertificateFactory X.509", e1); + } + } + + protected KeyManagerFactory getKeyManagerFactory() { + HttpClientProperties.Ssl ssl = properties.getSsl(); + try { + if (ssl.getKeyStore() != null && ssl.getKeyStore().length() > 0) { + KeyManagerFactory keyManagerFactory = KeyManagerFactory + .getInstance(KeyManagerFactory.getDefaultAlgorithm()); + char[] keyPassword = ssl.getKeyPassword() != null ? ssl.getKeyPassword().toCharArray() : null; + + if (keyPassword == null && ssl.getKeyStorePassword() != null) { + keyPassword = ssl.getKeyStorePassword().toCharArray(); + } + + keyManagerFactory.init(this.createKeyStore(), keyPassword); + + return keyManagerFactory; + } + + return null; + } + catch (Exception e) { + throw new IllegalStateException(e); + } + } + + protected KeyStore createKeyStore() { + HttpClientProperties.Ssl ssl = properties.getSsl(); + try { + KeyStore store = ssl.getKeyStoreProvider() != null + ? KeyStore.getInstance(ssl.getKeyStoreType(), ssl.getKeyStoreProvider()) + : KeyStore.getInstance(ssl.getKeyStoreType()); + try { + URL url = ResourceUtils.getURL(ssl.getKeyStore()); + store.load(url.openStream(), + ssl.getKeyStorePassword() != null ? ssl.getKeyStorePassword().toCharArray() : null); + } + catch (Exception e) { + throw new RuntimeException("Could not load key store ' " + ssl.getKeyStore() + "'", e); + } + + return store; + } + catch (KeyStoreException | NoSuchProviderException e) { + throw new RuntimeException("Could not load KeyStore for given type and provider", e); + } + } + + protected void setTrustManager(SslContextBuilder sslContextBuilder, X509Certificate... trustedX509Certificates) { + sslContextBuilder.trustManager(trustedX509Certificates); + } + + protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) { + sslContextBuilder.trustManager(factory); + } + + protected ConnectionProvider buildConnectionProvider(HttpClientProperties properties) { + HttpClientProperties.Pool pool = properties.getPool(); + + ConnectionProvider connectionProvider; + if (pool.getType() == DISABLED) { + connectionProvider = ConnectionProvider.newConnection(); + } + else { + // create either Fixed or Elastic pool + ConnectionProvider.Builder builder = ConnectionProvider.builder(pool.getName()); + if (pool.getType() == FIXED) { + builder.maxConnections(pool.getMaxConnections()).pendingAcquireMaxCount(-1) + .pendingAcquireTimeout(Duration.ofMillis(pool.getAcquireTimeout())); + } + else { + // Elastic + builder.maxConnections(Integer.MAX_VALUE).pendingAcquireTimeout(Duration.ofMillis(0)) + .pendingAcquireMaxCount(-1); + } + + if (pool.getMaxIdleTime() != null) { + builder.maxIdleTime(pool.getMaxIdleTime()); + } + if (pool.getMaxLifeTime() != null) { + builder.maxLifeTime(pool.getMaxLifeTime()); + } + builder.evictInBackground(pool.getEvictionInterval()); + builder.metrics(pool.isMetrics()); + connectionProvider = builder.build(); + } + return connectionProvider; + } + +} diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java index f0e2490f..bdce83ea 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/config/HttpClientProperties.java @@ -483,6 +483,7 @@ public class HttpClientProperties { this.trustedX509Certificates = trustedX509; } + @Deprecated public X509Certificate[] getTrustedX509CertificatesForTrustManager() { try { CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); @@ -505,6 +506,7 @@ public class HttpClientProperties { } } + @Deprecated public KeyManagerFactory getKeyManagerFactory() { try { if (getKeyStore() != null && getKeyStore().length() > 0) { @@ -528,6 +530,7 @@ public class HttpClientProperties { } } + @Deprecated public KeyStore createKeyStore() { try { KeyStore store = getKeyStoreProvider() != null diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java index d1a27852..6c46143c 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/config/GatewayAutoConfigurationTests.java @@ -18,18 +18,30 @@ package org.springframework.cloud.gateway.config; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; +import javax.net.ssl.TrustManagerFactory; + +import io.netty.channel.ChannelOption; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; import org.junit.Test; +import reactor.netty.http.HttpProtocol; import reactor.netty.http.client.HttpClient; +import reactor.netty.http.client.HttpClientConfig; import reactor.netty.http.client.WebsocketClientSpec; import reactor.netty.http.server.WebsocketServerSpec; +import reactor.netty.resources.ConnectionProvider; +import reactor.netty.tcp.SslProvider; +import reactor.netty.transport.ProxyProvider; import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration; import org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration; @@ -48,6 +60,7 @@ import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Primary; import org.springframework.security.oauth2.client.ReactiveOAuth2AuthorizedClientManager; import org.springframework.web.filter.reactive.HiddenHttpMethodFilter; import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; @@ -76,21 +89,19 @@ public class GatewayAutoConfigurationTests { ServerPropertiesConfig.class)) .withPropertyValues("debug=true").run(context -> { assertThat(context).hasSingleBean(HttpClient.class); - assertThat(context).hasBean("gatewayHttpClient"); HttpClient httpClient = context.getBean(HttpClient.class); - /* - * FIXME: 2.1.0 HttpClientOptions options = httpClient.options(); - * - * PoolResources poolResources = options.getPoolResources(); - * assertThat(poolResources).isNotNull(); //TODO: howto test - * PoolResources - * - * ClientProxyOptions proxyOptions = options.getProxyOptions(); - * assertThat(proxyOptions).isNull(); - * - * SslContext sslContext = options.sslContext(); - * assertThat(sslContext).isNull(); - */ + CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class); + + assertThat(factory.connectionProvider).isNotNull(); + assertThat(factory.connectionProvider.maxConnections()).isEqualTo(Integer.MAX_VALUE); // elastic + + assertThat(factory.proxyProvider).isNull(); + assertThat(factory.sslConfigured).isFalse(); + + assertThat(httpClient.configuration().isAcceptGzip()).isFalse(); + assertThat(httpClient.configuration().loggingHandler()).isNull(); + assertThat(httpClient.configuration().options()) + .doesNotContainKey(ChannelOption.CONNECT_TIMEOUT_MILLIS); }); } @@ -107,6 +118,7 @@ public class GatewayAutoConfigurationTests { "spring.cloud.gateway.httpclient.pool.type=fixed", "spring.cloud.gateway.httpclient.pool.metrics=true", "spring.cloud.gateway.httpclient.compression=true", + "spring.cloud.gateway.httpclient.wiretap=true", // greater than integer max value "spring.cloud.gateway.httpclient.max-initial-line-length=2147483647", "spring.cloud.gateway.httpclient.proxy.host=myhost", @@ -114,27 +126,30 @@ public class GatewayAutoConfigurationTests { .run(context -> { assertThat(context).hasSingleBean(HttpClient.class); HttpClient httpClient = context.getBean(HttpClient.class); + CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class); HttpClientProperties properties = context.getBean(HttpClientProperties.class); assertThat(properties.getMaxInitialLineLength().toBytes()).isLessThanOrEqualTo(Integer.MAX_VALUE); assertThat(properties.isCompression()).isEqualTo(true); assertThat(properties.getPool().getEvictionInterval()).hasSeconds(10); assertThat(properties.getPool().isMetrics()).isEqualTo(true); - /* - * FIXME: 2.1.0 HttpClientOptions options = httpClient.options(); - * - * PoolResources poolResources = options.getPoolResources(); - * assertThat(poolResources).isNotNull(); //TODO: howto test - * PoolResources - * - * ClientProxyOptions proxyOptions = options.getProxyOptions(); - * assertThat(proxyOptions).isNotNull(); - * assertThat(proxyOptions.getAddress().get().getHostName()).isEqualTo - * ("myhost"); - * - * SslContext sslContext = options.sslContext(); - * assertThat(sslContext).isNotNull(); - */ - // TODO: howto test SslContext + + assertThat(httpClient.configuration().isAcceptGzip()).isTrue(); + assertThat(httpClient.configuration().loggingHandler()).isNotNull(); + assertThat(httpClient.configuration().options()).containsKey(ChannelOption.CONNECT_TIMEOUT_MILLIS); + assertThat(httpClient.configuration().options().get(ChannelOption.CONNECT_TIMEOUT_MILLIS)) + .isEqualTo(10); + + assertThat(factory.connectionProvider).isNotNull(); + // fixed pool + assertThat(factory.connectionProvider.maxConnections()) + .isEqualTo(ConnectionProvider.DEFAULT_POOL_MAX_CONNECTIONS); + + assertThat(factory.proxyProvider).isNotNull(); + assertThat(factory.proxyProvider.build().getAddress().get().getHostName()).isEqualTo("myhost"); + + assertThat(factory.sslConfigured).isTrue(); + assertThat(factory.insecureTrustManagerSet).isTrue(); + assertThat(context).hasSingleBean(ReactorNettyRequestUpgradeStrategy.class); ReactorNettyRequestUpgradeStrategy upgradeStrategy = context .getBean(ReactorNettyRequestUpgradeStrategy.class); @@ -244,6 +259,8 @@ public class GatewayAutoConfigurationTests { .withPropertyValues("server.http2.enabled=true").run(context -> { assertThat(context).hasSingleBean(GRPCRequestHeadersFilter.class); assertThat(context).hasSingleBean(GRPCResponseHeadersFilter.class); + HttpClient httpClient = context.getBean(HttpClient.class); + assertThat(httpClient.configuration().protocols()).contains(HttpProtocol.HTTP11, HttpProtocol.H2); }); } @@ -259,10 +276,86 @@ public class GatewayAutoConfigurationTests { }); } + @Test + public void insecureTrustManagerNotEnabledByDefaultWhenHTTP2Enabled() { + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, + SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, + HttpClientCustomizedConfig.class, ServerPropertiesConfig.class)) + .withPropertyValues("server.http2.enabled=true").run(context -> { + assertThat(context).hasSingleBean(HttpClient.class); + CustomHttpClientFactory factory = context.getBean(CustomHttpClientFactory.class); + assertThat(factory.insecureTrustManagerSet).isFalse(); + }); + } + + @Test + public void customHttpClientWorks() { + new ReactiveWebApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(WebFluxAutoConfiguration.class, MetricsAutoConfiguration.class, + SimpleMetricsExportAutoConfiguration.class, GatewayAutoConfiguration.class, + HttpClientCustomizedConfig.class, CustomHttpClientConfig.class)) + .run(context -> { + assertThat(context).hasSingleBean(HttpClient.class); + HttpClient httpClient = context.getBean(HttpClient.class); + assertThat(httpClient).isInstanceOf(CustomHttpClient.class); + }); + } + @Configuration @EnableConfigurationProperties(ServerProperties.class) + @AutoConfigureBefore(GatewayAutoConfiguration.class) protected static class ServerPropertiesConfig { + @Bean + @Primary + CustomHttpClientFactory customHttpClientFactory(HttpClientProperties properties, + ServerProperties serverProperties, List customizers) { + return new CustomHttpClientFactory(properties, serverProperties, customizers); + } + + } + + protected static class CustomHttpClientFactory extends HttpClientFactory { + + boolean insecureTrustManagerSet; + + boolean sslConfigured; + + private ConnectionProvider connectionProvider; + + private ProxyProvider.Builder proxyProvider; + + public CustomHttpClientFactory(HttpClientProperties properties, ServerProperties serverProperties, + List customizers) { + super(properties, serverProperties, customizers); + } + + @Override + protected ConnectionProvider buildConnectionProvider(HttpClientProperties properties) { + connectionProvider = super.buildConnectionProvider(properties); + return connectionProvider; + } + + @Override + protected ProxyProvider.Builder configureProxyProvider(HttpClientProperties.Proxy proxy, + ProxyProvider.TypeSpec proxySpec) { + proxyProvider = super.configureProxyProvider(proxy, proxySpec); + return proxyProvider; + } + + @Override + protected void configureSslContext(HttpClientProperties.Ssl ssl, SslProvider.SslContextSpec sslContextSpec) { + sslConfigured = true; + super.configureSslContext(ssl, sslContextSpec); + } + + @Override + protected void setTrustManager(SslContextBuilder sslContextBuilder, TrustManagerFactory factory) { + insecureTrustManagerSet = factory == InsecureTrustManagerFactory.INSTANCE; + super.setTrustManager(sslContextBuilder, factory); + } + } @EnableAutoConfiguration @@ -271,6 +364,33 @@ public class GatewayAutoConfigurationTests { } + @EnableAutoConfiguration + @SpringBootConfiguration + @EnableConfigurationProperties(ServerProperties.class) + @AutoConfigureBefore(GatewayAutoConfiguration.class) + protected static class CustomHttpClientConfig { + + @Bean + public HttpClient customHttpClient() { + return new CustomHttpClient(); + } + + } + + protected static class CustomHttpClient extends HttpClient { + + @Override + public HttpClientConfig configuration() { + return null; + } + + @Override + protected HttpClient duplicate() { + return this; + } + + } + @EnableAutoConfiguration @SpringBootConfiguration protected static class RouteLocatorBuilderConfig { From d8c255eddf4eb5f80ba027329227b0d9e2cd9698 Mon Sep 17 00:00:00 2001 From: spencergibb Date: Tue, 8 Feb 2022 14:30:48 -0500 Subject: [PATCH 2/9] Updates ShortcutConfigurable to use custom EvaluationContext. It uses a SimpleEvaluationContext as a delegate that adds a BeanResolver for bean references in SpEL. --- .../gateway/support/ShortcutConfigurable.java | 85 ++++++++++++++++++- .../support/ShortcutConfigurableTests.java | 19 +++++ 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java index 43456533..22a2d1ea 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java @@ -25,10 +25,21 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.expression.BeanResolver; +import org.springframework.expression.ConstructorResolver; +import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; +import org.springframework.expression.MethodResolver; +import org.springframework.expression.OperatorOverloader; +import org.springframework.expression.PropertyAccessor; +import org.springframework.expression.TypeComparator; +import org.springframework.expression.TypeConverter; +import org.springframework.expression.TypeLocator; +import org.springframework.expression.TypedValue; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.StandardEvaluationContext; +import org.springframework.expression.spel.support.SimpleEvaluationContext; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; /** @@ -54,8 +65,7 @@ public interface ShortcutConfigurable { } if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) { // assume it's spel - StandardEvaluationContext context = new StandardEvaluationContext(); - context.setBeanResolver(new BeanFactoryResolver(beanFactory)); + GatewayEvaluationContext context = new GatewayEvaluationContext(new BeanFactoryResolver(beanFactory)); Expression expression = parser.parseExpression(entryValue, new TemplateParserContext()); value = expression.getValue(context); } @@ -148,4 +158,73 @@ public interface ShortcutConfigurable { } + class GatewayEvaluationContext implements EvaluationContext { + + private final BeanFactoryResolver beanFactoryResolver; + + private SimpleEvaluationContext delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + + public GatewayEvaluationContext(BeanFactoryResolver beanFactoryResolver) { + this.beanFactoryResolver = beanFactoryResolver; + } + + @Override + public TypedValue getRootObject() { + return delegate.getRootObject(); + } + + @Override + public List getPropertyAccessors() { + return delegate.getPropertyAccessors(); + } + + @Override + public List getConstructorResolvers() { + return delegate.getConstructorResolvers(); + } + + @Override + public List getMethodResolvers() { + return delegate.getMethodResolvers(); + } + + @Override + @Nullable + public BeanResolver getBeanResolver() { + return this.beanFactoryResolver; + } + + @Override + public TypeLocator getTypeLocator() { + return delegate.getTypeLocator(); + } + + @Override + public TypeConverter getTypeConverter() { + return delegate.getTypeConverter(); + } + + @Override + public TypeComparator getTypeComparator() { + return delegate.getTypeComparator(); + } + + @Override + public OperatorOverloader getOperatorOverloader() { + return delegate.getOperatorOverloader(); + } + + @Override + public void setVariable(String name, Object value) { + delegate.setVariable(name, value); + } + + @Override + @Nullable + public Object lookupVariable(String name) { + return delegate.lookupVariable(name); + } + + } + } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java index 54ac5dbc..158959bc 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java @@ -30,10 +30,12 @@ import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType; import org.springframework.context.annotation.Bean; +import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.test.context.junit4.SpringRunner; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; @RunWith(SpringRunner.class) @SpringBootTest @@ -44,6 +46,23 @@ public class ShortcutConfigurableTests { private SpelExpressionParser parser; + @Test + public void testNormalizeDefaultTypeWithSpelAndInvalidInputFails() { + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("bean", "#{T(java.lang.Runtime).getRuntime().exec(\"touch /tmp/x\")}"); + args.put("arg1", "val1"); + assertThatThrownBy(() -> { + ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + }).isInstanceOf(SpelEvaluationException.class); + } + @Test public void testNormalizeDefaultTypeWithSpel() { parser = new SpelExpressionParser(); From c76b7753709ef2b70229d72b1adfd2dbcae08c7e Mon Sep 17 00:00:00 2001 From: spencergibb Date: Mon, 14 Feb 2022 18:42:21 -0500 Subject: [PATCH 3/9] Adds spring.cloud.gateway.restrictive-property-accessor.enabled To disable the restrictive accessor, set spring.cloud.gateway.restrictive-property-accessor.enabled=false. --- .../gateway/support/ShortcutConfigurable.java | 29 +++++- ...itional-spring-configuration-metadata.json | 6 ++ .../support/ShortcutConfigurableTests.java | 92 +++++++++++++++++++ 3 files changed, 123 insertions(+), 4 deletions(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java index 22a2d1ea..a620e48b 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/support/ShortcutConfigurable.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.expression.BeanFactoryResolver; +import org.springframework.core.env.Environment; import org.springframework.expression.BeanResolver; import org.springframework.expression.ConstructorResolver; import org.springframework.expression.EvaluationContext; @@ -38,6 +39,7 @@ import org.springframework.expression.TypeLocator; import org.springframework.expression.TypedValue; import org.springframework.expression.common.TemplateParserContext; import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.ReflectivePropertyAccessor; import org.springframework.expression.spel.support.SimpleEvaluationContext; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -65,7 +67,7 @@ public interface ShortcutConfigurable { } if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) { // assume it's spel - GatewayEvaluationContext context = new GatewayEvaluationContext(new BeanFactoryResolver(beanFactory)); + GatewayEvaluationContext context = new GatewayEvaluationContext(beanFactory); Expression expression = parser.parseExpression(entryValue, new TemplateParserContext()); value = expression.getValue(context); } @@ -162,10 +164,20 @@ public interface ShortcutConfigurable { private final BeanFactoryResolver beanFactoryResolver; - private SimpleEvaluationContext delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + private final SimpleEvaluationContext delegate; - public GatewayEvaluationContext(BeanFactoryResolver beanFactoryResolver) { - this.beanFactoryResolver = beanFactoryResolver; + public GatewayEvaluationContext(BeanFactory beanFactory) { + this.beanFactoryResolver = new BeanFactoryResolver(beanFactory); + Environment env = beanFactory.getBean(Environment.class); + boolean restrictive = env.getProperty("spring.cloud.gateway.restrictive-property-accessor.enabled", + Boolean.class, true); + if (restrictive) { + delegate = SimpleEvaluationContext.forPropertyAccessors(new RestrictivePropertyAccessor()) + .withMethodResolvers((context, targetObject, name, argumentTypes) -> null).build(); + } + else { + delegate = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + } } @Override @@ -227,4 +239,13 @@ public interface ShortcutConfigurable { } + class RestrictivePropertyAccessor extends ReflectivePropertyAccessor { + + @Override + public boolean canRead(EvaluationContext context, Object target, String name) { + return false; + } + + } + } diff --git a/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json index 0e159b86..42e76321 100644 --- a/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/spring-cloud-gateway-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -365,6 +365,12 @@ "type": "java.lang.Integer", "description": "The order of RoutePredicateHandlerMapping.", "defaultValue": "1" + }, + { + "name": "spring.cloud.gateway.restrictive-property-accessor.enabled", + "type": "java.lang.Boolean", + "description": "Restricts method and property access in SpEL.", + "defaultValue": "true" } ] } diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java index 158959bc..42f7ef19 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java @@ -28,8 +28,10 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType; import org.springframework.context.annotation.Bean; +import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.expression.spel.SpelEvaluationException; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.test.context.junit4.SpringRunner; @@ -44,6 +46,9 @@ public class ShortcutConfigurableTests { @Autowired BeanFactory beanFactory; + @Autowired + ConfigurableEnvironment env; + private SpelExpressionParser parser; @Test @@ -63,6 +68,42 @@ public class ShortcutConfigurableTests { }).isInstanceOf(SpelEvaluationException.class); } + @Test + public void testNormalizeDefaultTypeWithSpelAndInvalidPropertyReferenceFails() { + TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=true").applyTo(env); + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("barproperty", "#{@bar.getInt}"); + args.put("arg1", "val1"); + assertThatThrownBy(() -> { + ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + }).isInstanceOf(SpelEvaluationException.class); + } + + @Test + public void testNormalizeDefaultTypeWithSpelAndInvalidMethodReferenceFails() { + TestPropertyValues.of("ispring.cloud.gateway.restrictve-property-accessor.enabled=true").applyTo(env); + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("barmethod", "#{@bar.myMethod}"); + args.put("arg1", "val1"); + assertThatThrownBy(() -> { + ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + }).isInstanceOf(SpelEvaluationException.class); + } + @Test public void testNormalizeDefaultTypeWithSpel() { parser = new SpelExpressionParser(); @@ -79,6 +120,40 @@ public class ShortcutConfigurableTests { assertThat(map).isNotNull().containsEntry("bean", 42).containsEntry("arg1", "val1"); } + @Test + public void testNormalizeDefaultTypeWithSpelAndPropertyReferenceEnabled() { + TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=false").applyTo(env); + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("barproperty", "#{@bar.getInt}"); + args.put("arg1", "val1"); + Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + assertThat(map).isNotNull().containsEntry("barproperty", 42).containsEntry("arg1", "val1"); + } + + @Test + public void testNormalizeDefaultTypeWithSpelAndMethodReferenceEnabled() { + TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=false").applyTo(env); + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("barmethod", "#{@bar.myMethod}"); + args.put("arg1", "val1"); + Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + assertThat(map).isNotNull().containsEntry("barmethod", 42).containsEntry("arg1", "val1"); + } + @Test @SuppressWarnings("unchecked") public void testNormalizeGatherListTypeWithSpel() { @@ -155,6 +230,23 @@ public class ShortcutConfigurableTests { return 42; } + @Bean + public Bar bar() { + return new Bar(); + } + + } + + protected static class Bar { + + public int getInt() { + return 42; + } + + public int myMethod() { + return 42; + } + } } From 849c0d6a64382d0c05bafca6e9ac481d134f78ef Mon Sep 17 00:00:00 2001 From: spencergibb Date: Wed, 16 Feb 2022 14:28:08 -0500 Subject: [PATCH 4/9] Separates ShortcutConfigurableNonRestrictiveTests --- ...ortcutConfigurableNonRestrictiveTests.java | 110 ++++++++++++++++++ .../support/ShortcutConfigurableTests.java | 37 ------ 2 files changed, 110 insertions(+), 37 deletions(-) create mode 100644 spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java new file mode 100644 index 00000000..cd5c9048 --- /dev/null +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableNonRestrictiveTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2013-2020 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 + * + * https://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.gateway.support; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType; +import org.springframework.context.annotation.Bean; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(properties = "spring.cloud.gateway.restrictive-property-accessor.enabled=false") +public class ShortcutConfigurableNonRestrictiveTests { + + @Autowired + BeanFactory beanFactory; + + @Autowired + ConfigurableEnvironment env; + + private SpelExpressionParser parser; + + @Test + public void testNormalizeDefaultTypeWithSpelAndPropertyReferenceEnabled() { + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("barproperty", "#{@bar.getInt}"); + args.put("arg1", "val1"); + Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + assertThat(map).isNotNull().containsEntry("barproperty", 42).containsEntry("arg1", "val1"); + } + + @Test + public void testNormalizeDefaultTypeWithSpelAndMethodReferenceEnabled() { + parser = new SpelExpressionParser(); + ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { + @Override + public List shortcutFieldOrder() { + return Arrays.asList("bean", "arg1"); + } + }; + Map args = new HashMap<>(); + args.put("barmethod", "#{@bar.myMethod}"); + args.put("arg1", "val1"); + Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); + assertThat(map).isNotNull().containsEntry("barmethod", 42).containsEntry("arg1", "val1"); + } + + @SpringBootConfiguration + protected static class TestConfig { + + @Bean + public Integer foo() { + return 42; + } + + @Bean + public Bar bar() { + return new Bar(); + } + + } + + protected static class Bar { + + public int getInt() { + return 42; + } + + public int myMethod() { + return 42; + } + + } + +} diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java index 42f7ef19..81dadfd8 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/support/ShortcutConfigurableTests.java @@ -28,7 +28,6 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringBootConfiguration; import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.boot.test.util.TestPropertyValues; import org.springframework.cloud.gateway.support.ShortcutConfigurable.ShortcutType; import org.springframework.context.annotation.Bean; import org.springframework.core.env.ConfigurableEnvironment; @@ -70,7 +69,6 @@ public class ShortcutConfigurableTests { @Test public void testNormalizeDefaultTypeWithSpelAndInvalidPropertyReferenceFails() { - TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=true").applyTo(env); parser = new SpelExpressionParser(); ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { @Override @@ -88,7 +86,6 @@ public class ShortcutConfigurableTests { @Test public void testNormalizeDefaultTypeWithSpelAndInvalidMethodReferenceFails() { - TestPropertyValues.of("ispring.cloud.gateway.restrictve-property-accessor.enabled=true").applyTo(env); parser = new SpelExpressionParser(); ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { @Override @@ -120,40 +117,6 @@ public class ShortcutConfigurableTests { assertThat(map).isNotNull().containsEntry("bean", 42).containsEntry("arg1", "val1"); } - @Test - public void testNormalizeDefaultTypeWithSpelAndPropertyReferenceEnabled() { - TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=false").applyTo(env); - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("barproperty", "#{@bar.getInt}"); - args.put("arg1", "val1"); - Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - assertThat(map).isNotNull().containsEntry("barproperty", 42).containsEntry("arg1", "val1"); - } - - @Test - public void testNormalizeDefaultTypeWithSpelAndMethodReferenceEnabled() { - TestPropertyValues.of("spring.cloud.gateway.restrictive-property-accessor.enabled=false").applyTo(env); - parser = new SpelExpressionParser(); - ShortcutConfigurable shortcutConfigurable = new ShortcutConfigurable() { - @Override - public List shortcutFieldOrder() { - return Arrays.asList("bean", "arg1"); - } - }; - Map args = new HashMap<>(); - args.put("barmethod", "#{@bar.myMethod}"); - args.put("arg1", "val1"); - Map map = ShortcutType.DEFAULT.normalize(args, shortcutConfigurable, parser, this.beanFactory); - assertThat(map).isNotNull().containsEntry("barmethod", 42).containsEntry("arg1", "val1"); - } - @Test @SuppressWarnings("unchecked") public void testNormalizeGatherListTypeWithSpel() { From d66ab832d18780e0e3bfa021f030807e3afa0a6e Mon Sep 17 00:00:00 2001 From: buildmaster Date: Thu, 17 Feb 2022 17:08:10 +0000 Subject: [PATCH 5/9] Update SNAPSHOT to 3.1.1 --- README.adoc | 3 ++- docs/pom.xml | 2 +- docs/src/main/asciidoc/_configprops.adoc | 2 ++ pom.xml | 8 ++++---- spring-cloud-gateway-dependencies/pom.xml | 4 ++-- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 14 files changed, 20 insertions(+), 17 deletions(-) diff --git a/README.adoc b/README.adoc index e21c76d3..a71c1c59 100644 --- a/README.adoc +++ b/README.adoc @@ -28,7 +28,8 @@ This project provides an API Gateway built on top of the Spring Ecosystem, inclu == Building -:jdkversion: 1.8 + +:jdkversion: 17 === Basic Compile and Test diff --git a/docs/pom.xml b/docs/pom.xml index acb20597..eeda34fb 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 spring-cloud-gateway-docs jar diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 123eabbf..240c9c30 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -122,12 +122,14 @@ |spring.cloud.gateway.predicate.read-body.enabled | `true` | Enables the read-body predicate. |spring.cloud.gateway.predicate.remote-addr.enabled | `true` | Enables the remote-addr predicate. |spring.cloud.gateway.predicate.weight.enabled | `true` | Enables the weight predicate. +|spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled | `true` | Enables the xforwarded-remote-addr predicate. |spring.cloud.gateway.redis-rate-limiter.burst-capacity-header | `X-RateLimit-Burst-Capacity` | The name of the header that returns the burst capacity configuration. |spring.cloud.gateway.redis-rate-limiter.config | | |spring.cloud.gateway.redis-rate-limiter.include-headers | `true` | Whether or not to include headers containing rate limiter information, defaults to true. |spring.cloud.gateway.redis-rate-limiter.remaining-header | `X-RateLimit-Remaining` | The name of the header that returns number of remaining requests during the current second. |spring.cloud.gateway.redis-rate-limiter.replenish-rate-header | `X-RateLimit-Replenish-Rate` | The name of the header that returns the replenish rate configuration. |spring.cloud.gateway.redis-rate-limiter.requested-tokens-header | `X-RateLimit-Requested-Tokens` | The name of the header that returns the requested tokens configuration. +|spring.cloud.gateway.restrictive-property-accessor.enabled | `true` | Restricts method and property access in SpEL. |spring.cloud.gateway.routes | | List of Routes. |spring.cloud.gateway.set-status.original-status-header-name | | The name of the header which contains http code of the proxied request. |spring.cloud.gateway.streaming-media-types | | diff --git a/pom.xml b/pom.xml index 0d5ede1d..3271c856 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 pom Spring Cloud Gateway @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 3.1.1-SNAPSHOT + 3.1.1 @@ -54,8 +54,8 @@ 1.0.6.RELEASE 1.8 1.0.0 - 2.1.1-SNAPSHOT - 3.1.1-SNAPSHOT + 2.1.1 + 3.1.1 1.16.3 diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index c3d75643..ae1b317f 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,12 +6,12 @@ spring-cloud-dependencies-parent org.springframework.cloud - 3.1.0 + 3.1.1 spring-cloud-gateway-dependencies - 3.1.1-SNAPSHOT + 3.1.1 pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index 6e1698e9..fbf78a35 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1-SNAPSHOT + 3.1.1 .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index 33038ebf..a686cd45 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1-SNAPSHOT + 3.1.1 .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index a46afcf0..6c20aef1 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1-SNAPSHOT + 3.1.1 .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index bb38718f..6f2ada61 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index 7a4495f8..c8c53a9d 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 1a42303d..65ccaa26 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 .. diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index 5588e814..c705897d 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index 7b796c32..d8cf4901 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 .. diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 79e80495..0d8caf27 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.1 .. spring-cloud-starter-gateway From d2652f9e5196c53d619e61bdb8f01e87f38d3ed0 Mon Sep 17 00:00:00 2001 From: buildmaster Date: Thu, 17 Feb 2022 17:10:06 +0000 Subject: [PATCH 6/9] Going back to snapshots --- README.adoc | 3 +-- docs/pom.xml | 2 +- docs/src/main/asciidoc/_configprops.adoc | 2 -- pom.xml | 8 ++++---- spring-cloud-gateway-dependencies/pom.xml | 4 ++-- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 14 files changed, 17 insertions(+), 20 deletions(-) diff --git a/README.adoc b/README.adoc index a71c1c59..e21c76d3 100644 --- a/README.adoc +++ b/README.adoc @@ -28,8 +28,7 @@ This project provides an API Gateway built on top of the Spring Ecosystem, inclu == Building - -:jdkversion: 17 +:jdkversion: 1.8 === Basic Compile and Test diff --git a/docs/pom.xml b/docs/pom.xml index eeda34fb..acb20597 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT spring-cloud-gateway-docs jar diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 240c9c30..123eabbf 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -122,14 +122,12 @@ |spring.cloud.gateway.predicate.read-body.enabled | `true` | Enables the read-body predicate. |spring.cloud.gateway.predicate.remote-addr.enabled | `true` | Enables the remote-addr predicate. |spring.cloud.gateway.predicate.weight.enabled | `true` | Enables the weight predicate. -|spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled | `true` | Enables the xforwarded-remote-addr predicate. |spring.cloud.gateway.redis-rate-limiter.burst-capacity-header | `X-RateLimit-Burst-Capacity` | The name of the header that returns the burst capacity configuration. |spring.cloud.gateway.redis-rate-limiter.config | | |spring.cloud.gateway.redis-rate-limiter.include-headers | `true` | Whether or not to include headers containing rate limiter information, defaults to true. |spring.cloud.gateway.redis-rate-limiter.remaining-header | `X-RateLimit-Remaining` | The name of the header that returns number of remaining requests during the current second. |spring.cloud.gateway.redis-rate-limiter.replenish-rate-header | `X-RateLimit-Replenish-Rate` | The name of the header that returns the replenish rate configuration. |spring.cloud.gateway.redis-rate-limiter.requested-tokens-header | `X-RateLimit-Requested-Tokens` | The name of the header that returns the requested tokens configuration. -|spring.cloud.gateway.restrictive-property-accessor.enabled | `true` | Restricts method and property access in SpEL. |spring.cloud.gateway.routes | | List of Routes. |spring.cloud.gateway.set-status.original-status-header-name | | The name of the header which contains http code of the proxied request. |spring.cloud.gateway.streaming-media-types | | diff --git a/pom.xml b/pom.xml index 3271c856..0d5ede1d 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT pom Spring Cloud Gateway @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 3.1.1 + 3.1.1-SNAPSHOT @@ -54,8 +54,8 @@ 1.0.6.RELEASE 1.8 1.0.0 - 2.1.1 - 3.1.1 + 2.1.1-SNAPSHOT + 3.1.1-SNAPSHOT 1.16.3 diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index ae1b317f..c3d75643 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,12 +6,12 @@ spring-cloud-dependencies-parent org.springframework.cloud - 3.1.1 + 3.1.0 spring-cloud-gateway-dependencies - 3.1.1 + 3.1.1-SNAPSHOT pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index fbf78a35..6e1698e9 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1 + 3.1.1-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index a686cd45..33038ebf 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1 + 3.1.1-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index 6c20aef1..a46afcf0 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1 + 3.1.1-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index 6f2ada61..bb38718f 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index c8c53a9d..7a4495f8 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 65ccaa26..1a42303d 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT .. diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index c705897d..5588e814 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index d8cf4901..7b796c32 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT .. diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 0d8caf27..79e80495 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1 + 3.1.1-SNAPSHOT .. spring-cloud-starter-gateway From d5ed2a3c6426a75e5a05cd8d55bd8eec08bc5dc2 Mon Sep 17 00:00:00 2001 From: buildmaster Date: Thu, 17 Feb 2022 17:10:07 +0000 Subject: [PATCH 7/9] Bumping versions to 3.1.2-SNAPSHOT after release --- docs/pom.xml | 2 +- pom.xml | 8 ++++---- spring-cloud-gateway-dependencies/pom.xml | 4 ++-- spring-cloud-gateway-integration-tests/grpc/pom.xml | 2 +- spring-cloud-gateway-integration-tests/http2/pom.xml | 2 +- .../mvc-failure-analyzer/pom.xml | 2 +- spring-cloud-gateway-integration-tests/pom.xml | 2 +- spring-cloud-gateway-mvc/pom.xml | 2 +- spring-cloud-gateway-sample/pom.xml | 2 +- spring-cloud-gateway-server/pom.xml | 2 +- spring-cloud-gateway-webflux/pom.xml | 2 +- spring-cloud-starter-gateway/pom.xml | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/pom.xml b/docs/pom.xml index acb20597..dbe719b9 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT spring-cloud-gateway-docs jar diff --git a/pom.xml b/pom.xml index 0d5ede1d..72891236 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT pom Spring Cloud Gateway @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 3.1.1-SNAPSHOT + 3.1.1 @@ -54,8 +54,8 @@ 1.0.6.RELEASE 1.8 1.0.0 - 2.1.1-SNAPSHOT - 3.1.1-SNAPSHOT + 2.1.2-SNAPSHOT + 3.1.2-SNAPSHOT 1.16.3 diff --git a/spring-cloud-gateway-dependencies/pom.xml b/spring-cloud-gateway-dependencies/pom.xml index c3d75643..078c327b 100644 --- a/spring-cloud-gateway-dependencies/pom.xml +++ b/spring-cloud-gateway-dependencies/pom.xml @@ -6,12 +6,12 @@ spring-cloud-dependencies-parent org.springframework.cloud - 3.1.0 + 3.1.2-SNAPSHOT spring-cloud-gateway-dependencies - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT pom spring-cloud-gateway-dependencies diff --git a/spring-cloud-gateway-integration-tests/grpc/pom.xml b/spring-cloud-gateway-integration-tests/grpc/pom.xml index 6e1698e9..b344842b 100644 --- a/spring-cloud-gateway-integration-tests/grpc/pom.xml +++ b/spring-cloud-gateway-integration-tests/grpc/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/http2/pom.xml b/spring-cloud-gateway-integration-tests/http2/pom.xml index 33038ebf..5d782b8c 100644 --- a/spring-cloud-gateway-integration-tests/http2/pom.xml +++ b/spring-cloud-gateway-integration-tests/http2/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml index a46afcf0..d934996d 100644 --- a/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml +++ b/spring-cloud-gateway-integration-tests/mvc-failure-analyzer/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway-integration-tests - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. diff --git a/spring-cloud-gateway-integration-tests/pom.xml b/spring-cloud-gateway-integration-tests/pom.xml index bb38718f..3a022f0a 100644 --- a/spring-cloud-gateway-integration-tests/pom.xml +++ b/spring-cloud-gateway-integration-tests/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. diff --git a/spring-cloud-gateway-mvc/pom.xml b/spring-cloud-gateway-mvc/pom.xml index 7a4495f8..f40969c8 100644 --- a/spring-cloud-gateway-mvc/pom.xml +++ b/spring-cloud-gateway-mvc/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. diff --git a/spring-cloud-gateway-sample/pom.xml b/spring-cloud-gateway-sample/pom.xml index 1a42303d..26727f3c 100644 --- a/spring-cloud-gateway-sample/pom.xml +++ b/spring-cloud-gateway-sample/pom.xml @@ -16,7 +16,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. diff --git a/spring-cloud-gateway-server/pom.xml b/spring-cloud-gateway-server/pom.xml index 5588e814..eb1d94d7 100644 --- a/spring-cloud-gateway-server/pom.xml +++ b/spring-cloud-gateway-server/pom.xml @@ -7,7 +7,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. spring-cloud-gateway-server diff --git a/spring-cloud-gateway-webflux/pom.xml b/spring-cloud-gateway-webflux/pom.xml index 7b796c32..e4ed2f3b 100644 --- a/spring-cloud-gateway-webflux/pom.xml +++ b/spring-cloud-gateway-webflux/pom.xml @@ -11,7 +11,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. diff --git a/spring-cloud-starter-gateway/pom.xml b/spring-cloud-starter-gateway/pom.xml index 79e80495..8a63cc20 100644 --- a/spring-cloud-starter-gateway/pom.xml +++ b/spring-cloud-starter-gateway/pom.xml @@ -6,7 +6,7 @@ org.springframework.cloud spring-cloud-gateway - 3.1.1-SNAPSHOT + 3.1.2-SNAPSHOT .. spring-cloud-starter-gateway From 8d32e3aa8e66ce8606aae12a76a2f5aa21f0838c Mon Sep 17 00:00:00 2001 From: buildmaster Date: Wed, 23 Feb 2022 21:12:03 +0000 Subject: [PATCH 8/9] Bumping versions --- README.adoc | 3 ++- docs/src/main/asciidoc/_configprops.adoc | 2 ++ pom.xml | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.adoc b/README.adoc index e21c76d3..a71c1c59 100644 --- a/README.adoc +++ b/README.adoc @@ -28,7 +28,8 @@ This project provides an API Gateway built on top of the Spring Ecosystem, inclu == Building -:jdkversion: 1.8 + +:jdkversion: 17 === Basic Compile and Test diff --git a/docs/src/main/asciidoc/_configprops.adoc b/docs/src/main/asciidoc/_configprops.adoc index 123eabbf..240c9c30 100644 --- a/docs/src/main/asciidoc/_configprops.adoc +++ b/docs/src/main/asciidoc/_configprops.adoc @@ -122,12 +122,14 @@ |spring.cloud.gateway.predicate.read-body.enabled | `true` | Enables the read-body predicate. |spring.cloud.gateway.predicate.remote-addr.enabled | `true` | Enables the remote-addr predicate. |spring.cloud.gateway.predicate.weight.enabled | `true` | Enables the weight predicate. +|spring.cloud.gateway.predicate.xforwarded-remote-addr.enabled | `true` | Enables the xforwarded-remote-addr predicate. |spring.cloud.gateway.redis-rate-limiter.burst-capacity-header | `X-RateLimit-Burst-Capacity` | The name of the header that returns the burst capacity configuration. |spring.cloud.gateway.redis-rate-limiter.config | | |spring.cloud.gateway.redis-rate-limiter.include-headers | `true` | Whether or not to include headers containing rate limiter information, defaults to true. |spring.cloud.gateway.redis-rate-limiter.remaining-header | `X-RateLimit-Remaining` | The name of the header that returns number of remaining requests during the current second. |spring.cloud.gateway.redis-rate-limiter.replenish-rate-header | `X-RateLimit-Replenish-Rate` | The name of the header that returns the replenish rate configuration. |spring.cloud.gateway.redis-rate-limiter.requested-tokens-header | `X-RateLimit-Requested-Tokens` | The name of the header that returns the requested tokens configuration. +|spring.cloud.gateway.restrictive-property-accessor.enabled | `true` | Restricts method and property access in SpEL. |spring.cloud.gateway.routes | | List of Routes. |spring.cloud.gateway.set-status.original-status-header-name | | The name of the header which contains http code of the proxied request. |spring.cloud.gateway.streaming-media-types | | diff --git a/pom.xml b/pom.xml index 72891236..15f7f7de 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,7 @@ org.springframework.cloud spring-cloud-build - 3.1.1 + 3.1.2-SNAPSHOT From 0dd2a47d8d6133af46ddfb7140d851b586623993 Mon Sep 17 00:00:00 2001 From: tommas Date: Fri, 11 Mar 2022 00:31:10 +0800 Subject: [PATCH 9/9] Merges upstream headers with that defined by gateway server, not overwrite them. Fixes gh-2541 Fixes gh-2547 --- .../cloud/gateway/filter/NettyRoutingFilter.java | 2 +- .../filter/NettyRoutingFilterIntegrationTests.java | 10 ++++++++++ .../resources/application-netty-routing-filter.yml | 11 +++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java index d0f12acb..aa26b0b8 100644 --- a/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java +++ b/spring-cloud-gateway-server/src/main/java/org/springframework/cloud/gateway/filter/NettyRoutingFilter.java @@ -181,7 +181,7 @@ public class NettyRoutingFilter implements GlobalFilter, Ordered { exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet()); - response.getHeaders().putAll(filteredResponseHeaders); + response.getHeaders().addAll(filteredResponseHeaders); return Mono.just(res); }); diff --git a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java index b1aec30e..5164949b 100644 --- a/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java +++ b/spring-cloud-gateway-server/src/test/java/org/springframework/cloud/gateway/filter/NettyRoutingFilterIntegrationTests.java @@ -132,6 +132,16 @@ public class NettyRoutingFilterIntegrationTests extends BaseWebClientTests { testClient.get().uri("/delay/2").exchange().expectStatus().isEqualTo(HttpStatus.OK); } + @Test + public void testHeadersAreClearedOnFallback() { + String header = "X-Test-SHOULD-MERGED-HEADER"; + String gatewayHeaderValue = "value-from-gateway"; + String upstreamHeaderValue = "value-from-upstream"; + testClient.post().uri("/responseheaders/200").header("Host", "www.mergeresponseheader.org") + .header(header, upstreamHeaderValue).exchange().expectHeader() + .valueEquals(header, gatewayHeaderValue, upstreamHeaderValue); + } + @EnableAutoConfiguration @SpringBootConfiguration @Import(DefaultTestConfig.class) diff --git a/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml b/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml index a8c2fdf7..8c25d969 100644 --- a/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml +++ b/spring-cloud-gateway-server/src/test/resources/application-netty-routing-filter.yml @@ -60,6 +60,17 @@ spring: metadata: response-timeout: notANumber + # ===================================== + - id: per_route_merge_response_headers + uri: ${test.uri} + predicates: + - Host=**.mergeresponseheader.org + - Path=/responseheaders/** + filters: + - AddResponseHeader=X-Test-SHOULD-MERGED-HEADER, value-from-gateway + metadata: + response-timeout: 1000 + # ===================================== # should be last and not follow alphabetical order - id: default_path_to_httpbin