From c8a693b50acaa283c844a278ddbef6b69120b86e Mon Sep 17 00:00:00 2001 From: Thirunavukkarasu Ravichandran Date: Sun, 14 Jul 2019 09:35:09 +0200 Subject: [PATCH] Adds remove request param filter. This removes a request parameter before sending downstream request. Fixes gh-1137 --- .../main/asciidoc/spring-cloud-gateway.adoc | 18 +++ .../config/GatewayAutoConfiguration.java | 6 + ...eRequestParameterGatewayFilterFactory.java | 81 +++++++++++++ .../route/builder/GatewayFilterSpec.java | 12 ++ ...rGatewayFilterFactoryIntegrationTests.java | 85 ++++++++++++++ ...estParameterGatewayFilterFactoryTests.java | 106 ++++++++++++++++++ 6 files changed, 308 insertions(+) create mode 100644 spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java create mode 100644 spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java create mode 100644 spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java diff --git a/docs/src/main/asciidoc/spring-cloud-gateway.adoc b/docs/src/main/asciidoc/spring-cloud-gateway.adoc index 16c8400f..b21a7357 100644 --- a/docs/src/main/asciidoc/spring-cloud-gateway.adoc +++ b/docs/src/main/asciidoc/spring-cloud-gateway.adoc @@ -803,6 +803,24 @@ To remove any kind of sensitive header you should configure this filter for any want to do so. In addition you can configure this filter once using `spring.cloud.gateway.default-filters` and have it applied to all routes. +=== RemoveRequestParameter GatewayFilter Factory +The RemoveRequestParameter GatewayFilter Factory takes a `name` parameter. It is the name of the query parameter to be removed. + +.application.yml +[source,yaml] +---- +spring: + cloud: + gateway: + routes: + - id: removerequestparameter_route + uri: https://example.org + filters: + - RemoveRequestParameter=foo +---- + +This will remove the `foo` parameter before it is sent downstream. + === RewritePath GatewayFilter Factory The RewritePath GatewayFilter Factory takes a path `regexp` parameter and a `replacement` parameter. This uses Java regular expressions for a flexible way to rewrite the request path. 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 7e5fa9f5..e605839a 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 @@ -72,6 +72,7 @@ import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterF import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; @@ -440,6 +441,11 @@ public class GatewayAutoConfiguration { return new RemoveRequestHeaderGatewayFilterFactory(); } + @Bean + public RemoveRequestParameterGatewayFilterFactory removeRequestParameterGatewayFilterFactory() { + return new RemoveRequestParameterGatewayFilterFactory(); + } + @Bean public RemoveResponseHeaderGatewayFilterFactory removeResponseHeaderGatewayFilterFactory() { return new RemoveResponseHeaderGatewayFilterFactory(); diff --git a/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java new file mode 100644 index 00000000..7e698a11 --- /dev/null +++ b/spring-cloud-gateway-core/src/main/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactory.java @@ -0,0 +1,81 @@ +/* + * Copyright 2013-2019 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.filter.factory; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; + +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.util.UriComponentsBuilder; + +import static org.springframework.cloud.gateway.support.GatewayToStringStyler.filterToStringCreator; +import static org.springframework.util.CollectionUtils.unmodifiableMultiValueMap; + +/** + * @author Thirunavukkarasu Ravichandran + */ +public class RemoveRequestParameterGatewayFilterFactory + extends AbstractGatewayFilterFactory { + + public RemoveRequestParameterGatewayFilterFactory() { + super(NameConfig.class); + } + + @Override + public List shortcutFieldOrder() { + return Arrays.asList(NAME_KEY); + } + + @Override + public GatewayFilter apply(NameConfig config) { + return new GatewayFilter() { + @Override + public Mono filter(ServerWebExchange exchange, + GatewayFilterChain chain) { + ServerHttpRequest request = exchange.getRequest(); + MultiValueMap queryParams = new LinkedMultiValueMap<>( + request.getQueryParams()); + queryParams.remove(config.getName()); + + URI newUri = UriComponentsBuilder.fromUri(request.getURI()) + .replaceQueryParams(unmodifiableMultiValueMap(queryParams)) + .build(true).toUri(); + + ServerHttpRequest updatedRequest = exchange.getRequest().mutate() + .uri(newUri).build(); + + return chain.filter(exchange.mutate().request(updatedRequest).build()); + } + + @Override + public String toString() { + return filterToStringCreator( + RemoveRequestParameterGatewayFilterFactory.this) + .append("name", config.getName()).toString(); + } + }; + } + +} 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..0cc2a0b7 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 @@ -47,6 +47,7 @@ import org.springframework.cloud.gateway.filter.factory.PrefixPathGatewayFilterF import org.springframework.cloud.gateway.filter.factory.PreserveHostHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveRequestHeaderGatewayFilterFactory; +import org.springframework.cloud.gateway.filter.factory.RemoveRequestParameterGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RemoveResponseHeaderGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestHeaderToRequestUriGatewayFilterFactory; import org.springframework.cloud.gateway.filter.factory.RequestRateLimiterGatewayFilterFactory; @@ -424,6 +425,17 @@ public class GatewayFilterSpec extends UriSpec { .apply(c -> c.setName(headerName))); } + /** + * A filter that will remove a request param before the request is routed by the + * Gateway. + * @param paramName the name of the header to remove + * @return a {@link GatewayFilterSpec} that can be used to apply additional filters + */ + public GatewayFilterSpec removeRequestParameter(String paramName) { + return filter(getBean(RemoveRequestParameterGatewayFilterFactory.class) + .apply(c -> c.setName(paramName))); + } + /** * A filter that will remove a response header before the Gateway returns the response * to the client. diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java new file mode 100644 index 00000000..9d8caab3 --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryIntegrationTests.java @@ -0,0 +1,85 @@ +/* + * Copyright 2013-2019 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.filter.factory; + +import java.util.Map; + +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.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; +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.test.annotation.DirtiesContext; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT; +import static org.springframework.cloud.gateway.test.TestUtils.getMap; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = RANDOM_PORT) +@DirtiesContext +public class RemoveRequestParameterGatewayFilterFactoryIntegrationTests + extends BaseWebClientTests { + + @Test + public void removeResponseHeaderFilterWorks() { + testClient.get().uri("/get?foo=bar&baz=bam") + .header("Host", "www.removerequestparamjava.org").exchange() + .expectStatus().isOk().expectBody(Map.class).consumeWith(result -> { + Map params = getMap(result.getResponseBody(), "args"); + assertThat(params).doesNotContainKey("foo"); + }); + } + + @Test + public void toStringFormat() { + NameConfig config = new NameConfig(); + config.setName("myname"); + GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory() + .apply(config); + assertThat(filter.toString()).contains("myname"); + } + + @EnableAutoConfiguration + @SpringBootConfiguration + @Import(DefaultTestConfig.class) + public static class TestConfig { + + @Value("${test.uri}") + String uri; + + @Bean + public RouteLocator testRouteLocator(RouteLocatorBuilder builder) { + return builder.routes().route("removerequestparam_java_test", r -> r + .path("/get").and().host("**.removerequestparamjava.org") + .filters(f -> f.prefixPath("/httpbin").removeRequestParameter("foo")) + .uri(uri)).build(); + } + + } + +} diff --git a/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java new file mode 100644 index 00000000..5cc6c62b --- /dev/null +++ b/spring-cloud-gateway-core/src/test/java/org/springframework/cloud/gateway/filter/factory/RemoveRequestParameterGatewayFilterFactoryTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2013-2019 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.filter.factory; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import reactor.core.publisher.Mono; + +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory.NameConfig; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.mock.http.server.reactive.MockServerHttpRequest; +import org.springframework.mock.web.server.MockServerWebExchange; +import org.springframework.web.server.ServerWebExchange; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Java6Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author Thirunavukkarasu Ravichandran + */ +public class RemoveRequestParameterGatewayFilterFactoryTests { + + private ServerWebExchange exchange; + + private GatewayFilterChain filterChain; + + private ArgumentCaptor captor; + + @Before + public void setUp() { + filterChain = mock(GatewayFilterChain.class); + captor = ArgumentCaptor.forClass(ServerWebExchange.class); + when(filterChain.filter(captor.capture())).thenReturn(Mono.empty()); + + } + + @Test + public void removeRequestParameterFilterWorks() { + MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") + .queryParam("foo", singletonList("bar")).build(); + exchange = MockServerWebExchange.from(request); + NameConfig config = new NameConfig(); + config.setName("foo"); + GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory() + .apply(config); + + filter.filter(exchange, filterChain); + + ServerHttpRequest actualRequest = captor.getValue().getRequest(); + assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); + } + + @Test + public void removeRequestParameterFilterWorksWhenParamIsNotPresentInRequest() { + MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") + .build(); + exchange = MockServerWebExchange.from(request); + NameConfig config = new NameConfig(); + config.setName("foo"); + GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory() + .apply(config); + + filter.filter(exchange, filterChain); + + ServerHttpRequest actualRequest = captor.getValue().getRequest(); + assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); + } + + @Test + public void removeRequestParameterFilterShouldOnlyRemoveSpecifiedParam() { + MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost") + .queryParam("foo", "bar").queryParam("abc", "xyz").build(); + exchange = MockServerWebExchange.from(request); + NameConfig config = new NameConfig(); + config.setName("foo"); + GatewayFilter filter = new RemoveRequestParameterGatewayFilterFactory() + .apply(config); + + filter.filter(exchange, filterChain); + + ServerHttpRequest actualRequest = captor.getValue().getRequest(); + assertThat(actualRequest.getQueryParams()).doesNotContainKey("foo"); + assertThat(actualRequest.getQueryParams()).containsEntry("abc", + singletonList("xyz")); + } + +}