diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java index 663294cc..ac3d6c3e 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/config/GatewayAutoConfiguration.java @@ -71,6 +71,7 @@ import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewa import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory; @@ -516,6 +517,11 @@ public class GatewayAutoConfiguration { return new RequestSizeGatewayFilterFactory(); } + @Bean + public RequestHeaderSizeGatewayFilterFactory requestHeaderSizeGatewayFilterFactory() { + return new RequestHeaderSizeGatewayFilterFactory(); + } + @Configuration @ConditionalOnClass(HttpClient.class) protected static class NettyConfiguration { diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java new file mode 100644 index 00000000..111fbf05 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactory.java @@ -0,0 +1,94 @@ +/* + * Copyright 2013-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.springframework.cloud.gateway.filter.factory; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; + +import java.util.List; +import java.util.Map; + +/** + * This filter validates the size of each Request Header in the request. If size of any of + * the request header is greater than the configured maxSize,it blocks the request. + * Default max size of request header is 16KB. + * + * @author Sakalya Deshpande + */ + +public class RequestHeaderSizeGatewayFilterFactory extends + AbstractGatewayFilterFactory { + + private static String ERROR = "Request Header/s size is larger than permissible limit." + + " Request Header/s size is %s where permissible limit is %s"; + + public RequestHeaderSizeGatewayFilterFactory() { + super(RequestHeaderSizeGatewayFilterFactory.Config.class); + } + + @Override + public GatewayFilter apply(RequestHeaderSizeGatewayFilterFactory.Config config) { + return (exchange, chain) -> { + ServerHttpRequest request = exchange.getRequest(); + HttpHeaders headers = request.getHeaders(); + Long headerSizeInBytes = 0L; + + for (Map.Entry> headerEntry : headers.entrySet()) { + List values = headerEntry.getValue(); + for (String value : values) { + headerSizeInBytes += Long.valueOf(value.getBytes().length); + } + } + + if (headerSizeInBytes > config.getMaxSize().toBytes()) { + exchange.getResponse() + .setStatusCode(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE); + exchange.getResponse().getHeaders().add("errorMessage", + getErrorMessage(headerSizeInBytes, config.getMaxSize())); + return exchange.getResponse().setComplete(); + + } + + return chain.filter(exchange); + }; + } + + private static String getErrorMessage(Long currentRequestSize, DataSize maxSize) { + return String.format(ERROR, DataSize.of(currentRequestSize, DataUnit.BYTES), + maxSize); + } + + public static class Config { + + private DataSize maxSize = DataSize.of(16000L, DataUnit.BYTES); + + public DataSize getMaxSize() { + return maxSize; + } + + public void setMaxSize(DataSize maxSize) { + this.maxSize = maxSize; + } + + } + +} \ No newline at end of file diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java index 1fa2093f..411bb47b 100644 --- a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/route/builder/GatewayFilterSpec.java @@ -48,6 +48,7 @@ import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewa import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.RequestHeaderSizeGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestSizeGatewayFilterFactory; @@ -68,6 +69,7 @@ import org.springframework.cloud.gateway.filter.ratelimit.RateLimiter; import org.springframework.cloud.gateway.route.Route; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; +import org.springframework.util.unit.DataSize; import org.springframework.web.server.ServerWebExchange; /** @@ -663,6 +665,15 @@ public class GatewayFilterSpec extends UriSpec { .apply(c -> c.setMaxSize(size))); } + /** + * A filter that sets the maximum permissible size of headers of Request. + * @param size the maximum size of header of request + * @return a {@link GatewayFilterSpec} that can be used to apply additional filters + */ + public GatewayFilterSpec setRequestHeaderSize(DataSize size) { + return filter(getBean(RequestHeaderSizeGatewayFilterFactory.class).apply(c -> c.setMaxSize(size))); + } + /** * Adds hystrix execution exception headers to fallback request. Depends on @{code * org.springframework.cloud::spring-cloud-starter-netflix-hystrix} being on the diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java new file mode 100644 index 00000000..2335367b --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RequestHeaderSizeGatewayFilterFactoryTest.java @@ -0,0 +1,60 @@ +package org.springframework.cloud.gateway.filter.factory; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.cloud.gateway.test.BaseWebClientTests; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpStatus; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.unit.DataSize; +import org.springframework.util.unit.DataUnit; + +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; + +/** + * @author Sakalya Deshpande + */ + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +@DirtiesContext +public class RequestHeaderSizeGatewayFilterFactoryTest extends BaseWebClientTests { + + private static final String responseMesssage = "Request Header/s size is larger than permissible limit. Request Header/s size is 73B where permissible limit is 46B"; + + @Test + public void setRequestSizeFilterWorks() { + testClient.get().uri("/headers").header("Host", "www.test.org") + .header("HeaderName", "Some Very Large Header Name").exchange() + .expectStatus().isEqualTo(HttpStatus.REQUEST_HEADER_FIELDS_TOO_LARGE) + .expectHeader().valueMatches("errorMessage", responseMesssage); + } + + @EnableAutoConfiguration + @SpringBootConfiguration + @Import(DefaultTestConfig.class) + public static class TestConfig { + + @Value("${test.uri}") + String uri; + + @Bean + public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { + return builder.routes().route("test_request_header_size", + r -> r.order(-1).host("**.test.org").filters( + f -> f.setRequestHeaderSize(DataSize.of(46L, DataUnit.BYTES))) + .uri(uri)) + .build(); + } + + } + +} \ No newline at end of file