diff --git a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java index 3ce71e9c9a8..6aa755f20b2 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java @@ -31,6 +31,7 @@ import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.util.CollectionUtils; import org.springframework.util.LinkedCaseInsensitiveMap; @@ -78,6 +79,8 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { private boolean removeOnly; + private boolean relativeRedirects; + public ForwardedHeaderFilter() { this.pathHelper = new UrlPathHelper(); @@ -89,13 +92,28 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter { /** * Enables mode in which any "Forwarded" or "X-Forwarded-*" headers are * removed only and the information in them ignored. - * @param removeOnly whether to discard and ingore forwarded headers + * @param removeOnly whether to discard and ignore forwarded headers * @since 4.3.9 */ public void setRemoveOnly(boolean removeOnly) { this.removeOnly = removeOnly; } + /** + * Use this property to enable relative redirects as explained in and also + * using the same response wrapper as {@link RelativeRedirectFilter} does. + * Or if both filters are used, only one will wrap the response. + *
By default, if this property is set to false, in which case calls to
+ * {@link HttpServletResponse#sendRedirect(String)} are overridden in order
+ * to turn relative into absolute URLs since (which Servlet containers are
+ * also required to do) also taking forwarded headers into consideration.
+ * @param relativeRedirects whether to use relative redirects
+ * @since 4.3.10
+ */
+ public void setRelativeRedirects(boolean relativeRedirects) {
+ this.relativeRedirects = relativeRedirects;
+ }
+
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
@@ -129,7 +147,9 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
}
else {
HttpServletRequest theRequest = new ForwardedHeaderExtractingRequest(request, this.pathHelper);
- HttpServletResponse theResponse = new ForwardedHeaderExtractingResponse(response, theRequest);
+ HttpServletResponse theResponse = (this.relativeRedirects ?
+ RelativeRedirectResponseWrapper.wrapIfNecessary(response, HttpStatus.SEE_OTHER) :
+ new ForwardedHeaderExtractingResponse(response, theRequest));
filterChain.doFilter(theRequest, theResponse);
}
}
@@ -142,7 +162,6 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
private final Map Note: While relative redirects are more efficient they
+ * may not work with reverse proxies under some configurations.
+ *
+ * @author Rob Winch
+ * @author Rossen Stoyanchev
+ * @since 4.3.10
+ */
+public class RelativeRedirectFilter extends OncePerRequestFilter {
+
+ private HttpStatus redirectStatus = HttpStatus.SEE_OTHER;
+
+
+ /**
+ * Set the default HTTP Status to use for redirects.
+ * By default this is {@link HttpStatus#SEE_OTHER}.
+ * @param status the 3xx redirect status to use
+ */
+ public void setRedirectStatus(HttpStatus status) {
+ Assert.notNull(status, "Property 'redirectStatus' is required");
+ Assert.isTrue(status.is3xxRedirection(), "Not a redirect status code");
+ this.redirectStatus = status;
+ }
+
+ /**
+ * Return the configured redirect status.
+ */
+ public HttpStatus getRedirectStatus() {
+ return this.redirectStatus;
+ }
+
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+
+ response = RelativeRedirectResponseWrapper.wrapIfNecessary(response, this.redirectStatus);
+ filterChain.doFilter(request, response);
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/web/filter/RelativeRedirectResponseWrapper.java b/spring-web/src/main/java/org/springframework/web/filter/RelativeRedirectResponseWrapper.java
new file mode 100644
index 00000000000..af95f1c4188
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/web/filter/RelativeRedirectResponseWrapper.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2002-2017 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.web.filter;
+
+import java.io.IOException;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpServletResponseWrapper;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.util.Assert;
+
+/**
+ * A response wrapper used for the implementation of
+ * {@link RelativeRedirectFilter} also shared with {@link ForwardedHeaderFilter}.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3.10
+ */
+class RelativeRedirectResponseWrapper extends HttpServletResponseWrapper {
+
+ private final HttpStatus redirectStatus;
+
+
+ private RelativeRedirectResponseWrapper(HttpServletResponse response, HttpStatus redirectStatus) {
+ super(response);
+ Assert.notNull(redirectStatus, "'redirectStatus' is required");
+ this.redirectStatus = redirectStatus;
+ }
+
+
+ @Override
+ public void sendRedirect(String location) throws IOException {
+ setStatus(this.redirectStatus.value());
+ setHeader(HttpHeaders.LOCATION, location);
+ }
+
+
+ public static HttpServletResponse wrapIfNecessary(HttpServletResponse response,
+ HttpStatus redirectStatus) {
+
+ return (hasWrapper(response) ? response : new RelativeRedirectResponseWrapper(response, redirectStatus));
+ }
+
+ private static boolean hasWrapper(ServletResponse response) {
+ if (response instanceof RelativeRedirectResponseWrapper) {
+ return true;
+ }
+ while (response instanceof HttpServletResponseWrapper) {
+ response = ((HttpServletResponseWrapper) response).getResponse();
+ if (response instanceof RelativeRedirectResponseWrapper) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
index 15aff6ffc12..4e20defb0e6 100644
--- a/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
+++ b/spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2016 the original author or authors.
+ * Copyright 2002-2017 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.
@@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.springframework.web.filter;
import java.io.IOException;
import java.util.Enumeration;
-
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
@@ -32,20 +32,18 @@ import org.springframework.mock.web.test.MockFilterChain;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
/**
* Unit tests for {@link ForwardedHeaderFilter}.
+ *
* @author Rossen Stoyanchev
* @author EddĂș MelĂ©ndez
* @author Rob Winch
*/
public class ForwardedHeaderFilterTests {
- private static final String X_FORWARDED_PROTO = "x-forwarded-proto"; // SPR-14372 (case insensitive)
+ private static final String X_FORWARDED_PROTO = "x-forwarded-proto"; // SPR-14372 (case insensitive)
private static final String X_FORWARDED_HOST = "x-forwarded-host";
private static final String X_FORWARDED_PORT = "x-forwarded-port";
private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix";
@@ -60,7 +58,7 @@ public class ForwardedHeaderFilterTests {
@Before
@SuppressWarnings("serial")
- public void setUp() throws Exception {
+ public void setup() throws Exception {
this.request = new MockHttpServletRequest();
this.request.setScheme("http");
this.request.setServerName("localhost");
@@ -185,9 +183,7 @@ public class ForwardedHeaderFilterTests {
@Test
public void caseInsensitiveForwardedPrefix() throws Exception {
this.request = new MockHttpServletRequest() {
-
// Make it case-sensitive (SPR-14372)
-
@Override
public String getHeader(String header) {
Enumeration