@@ -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

@@ -6,7 +6,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
</parent>
<artifactId>spring-cloud-gateway-docs</artifactId>
<packaging>jar</packaging>
@@ -70,6 +70,7 @@
|spring.cloud.gateway.global-filter.websocket-routing.enabled | `true` | Enables the websocket-routing global filter.
|spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping | `false` | If global CORS config should be added to the URL handler.
|spring.cloud.gateway.globalcors.cors-configurations | |
|spring.cloud.gateway.handler-mapping.order | `1` | The order of RoutePredicateHandlerMapping.
|spring.cloud.gateway.httpclient.compression | `false` | Enables compression for Netty HttpClient.
|spring.cloud.gateway.httpclient.connect-timeout | | The connect timeout in millis, the default is 45s.
|spring.cloud.gateway.httpclient.max-header-size | | The max response header size.
@@ -127,6 +128,7 @@
|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 | |
@@ -6,7 +6,7 @@

<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
<packaging>pom</packaging>

<name>Spring Cloud Gateway</name>
@@ -11,7 +11,7 @@
</parent>

<artifactId>spring-cloud-gateway-dependencies</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
<packaging>pom</packaging>

<name>spring-cloud-gateway-dependencies</name>
@@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway-integration-tests</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>

@@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>

@@ -11,7 +11,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
<relativePath>..</relativePath>
</parent>

@@ -16,7 +16,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>

@@ -7,7 +7,7 @@
<parent>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-gateway</artifactId>
<version>3.0.6</version>
<version>3.0.7</version>
<relativePath>..</relativePath> <!-- lookup parent from repository -->
</parent>
<artifactId>spring-cloud-gateway-server</artifactId>
@@ -251,6 +251,7 @@ public GlobalCorsProperties globalCorsProperties() {
}

@Bean
@ConditionalOnMissingBean
public RoutePredicateHandlerMapping routePredicateHandlerMapping(FilteringWebHandler webHandler,
RouteLocator routeLocator, GlobalCorsProperties globalCorsProperties, Environment environment) {
return new RoutePredicateHandlerMapping(webHandler, routeLocator, globalCorsProperties, environment);
@@ -20,6 +20,7 @@

import reactor.core.publisher.Mono;

import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.config.GlobalCorsProperties;
import org.springframework.cloud.gateway.route.Route;
import org.springframework.cloud.gateway.route.RouteLocator;
@@ -55,7 +56,7 @@ public RoutePredicateHandlerMapping(FilteringWebHandler webHandler, RouteLocator

this.managementPort = getPortProperty(environment, "management.server.");
this.managementPortType = getManagementPortType(environment);
setOrder(1);
setOrder(environment.getProperty(GatewayProperties.PREFIX + ".handler-mapping.order", Integer.class, 1));
setCorsConfigurations(globalCorsProperties.getCorsConfigurations());
}

@@ -25,10 +25,23 @@

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;
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.ReflectivePropertyAccessor;
import org.springframework.expression.spel.support.SimpleEvaluationContext;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
@@ -54,8 +67,7 @@ static Object getValue(SpelExpressionParser parser, BeanFactory beanFactory, Str
}
if (rawValue != null && rawValue.startsWith("#{") && entryValue.endsWith("}")) {
// assume it's spel
StandardEvaluationContext context = new StandardEvaluationContext();
context.setBeanResolver(new BeanFactoryResolver(beanFactory));
GatewayEvaluationContext context = new GatewayEvaluationContext(beanFactory);
Expression expression = parser.parseExpression(entryValue, new TemplateParserContext());
value = expression.getValue(context);
}
@@ -148,4 +160,92 @@ public abstract Map<String, Object> normalize(Map<String, String> args, Shortcut

}

class GatewayEvaluationContext implements EvaluationContext {

private final BeanFactoryResolver beanFactoryResolver;

private final SimpleEvaluationContext delegate;

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
public TypedValue getRootObject() {
return delegate.getRootObject();
}

@Override
public List<PropertyAccessor> getPropertyAccessors() {
return delegate.getPropertyAccessors();
}

@Override
public List<ConstructorResolver> getConstructorResolvers() {
return delegate.getConstructorResolvers();
}

@Override
public List<MethodResolver> 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);
}

}

class RestrictivePropertyAccessor extends ReflectivePropertyAccessor {

@Override
public boolean canRead(EvaluationContext context, Object target, String name) {
return false;
}

}

}
@@ -359,6 +359,18 @@
"type": "java.lang.Boolean",
"description": "If global CORS config should be added to the URL handler.",
"defaultValue": "false"
},
{
"name": "spring.cloud.gateway.handler-mapping.order",
"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"
}
]
}
@@ -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<String> shortcutFieldOrder() {
return Arrays.asList("bean", "arg1");
}
};
Map<String, String> args = new HashMap<>();
args.put("barproperty", "#{@bar.getInt}");
args.put("arg1", "val1");
Map<String, Object> 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<String> shortcutFieldOrder() {
return Arrays.asList("bean", "arg1");
}
};
Map<String, String> args = new HashMap<>();
args.put("barmethod", "#{@bar.myMethod}");
args.put("arg1", "val1");
Map<String, Object> 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;
}

}

}