Skip to content

Commit fadf04e

Browse files
rwinchrstoyanchev
authored andcommitted
Add RelativeRedirectFilter
1 parent 5f868b4 commit fadf04e

File tree

3 files changed

+172
-1
lines changed

3 files changed

+172
-1
lines changed

spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ public void setRemoveOnly(boolean removeOnly) {
102102
/**
103103
* Enables mode in which only the HttpServletRequest is modified. This means that
104104
* {@link HttpServletResponse#sendRedirect(String)} will only work when the application is configured to use
105-
* relative redirects. This can be done with Servlet Container specific setup. For example, using Tomcat's
105+
* relative redirects. This can be done by placing {@link RelativeRedirectFilter} after this Filter or Servlet
106+
* Container specific setup. For example, using Tomcat's
106107
* <a href="https://tomcat.apache.org/tomcat-8.0-doc/config/context.html#Common_Attributes">useRelativeRedirects</a>
107108
* attribute.
108109
*
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.filter;
18+
19+
import org.springframework.http.HttpHeaders;
20+
import org.springframework.http.HttpStatus;
21+
import org.springframework.util.Assert;
22+
23+
import javax.servlet.FilterChain;
24+
import javax.servlet.ServletException;
25+
import javax.servlet.http.HttpServletRequest;
26+
import javax.servlet.http.HttpServletResponse;
27+
import javax.servlet.http.HttpServletResponseWrapper;
28+
import java.io.IOException;
29+
30+
/**
31+
* Overrides the {@link HttpServletResponse#sendRedirect(String)} to set the "Location" header and the HTTP Status
32+
* directly to avoid the Servlet Container from creating an absolute URL. This allows redirects that have a relative
33+
* "Location" that ensures support for <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2">RFC 7231 Section
34+
* 7.1.2</a>. It should be noted that while relative redirects are more efficient, they may not work with reverse
35+
* proxies under some configurations.
36+
*
37+
* @author Rob Winch
38+
* @since 4.3.10
39+
*/
40+
public class RelativeRedirectFilter extends OncePerRequestFilter {
41+
private HttpStatus sendRedirectHttpStatus = HttpStatus.FOUND;
42+
43+
/**
44+
* Sets the HTTP Status to be used when {@code HttpServletResponse#sendRedirect(String)} is invoked.
45+
* @param sendRedirectHttpStatus the 3xx HTTP Status to be used when
46+
* {@code HttpServletResponse#sendRedirect(String)} is invoked. The default is {@code HttpStatus.FOUND}.
47+
*/
48+
public void setSendRedirectHttpStatus(HttpStatus sendRedirectHttpStatus) {
49+
Assert.notNull(sendRedirectHttpStatus, "HttpStatus is required");
50+
if(!sendRedirectHttpStatus.is3xxRedirection()) {
51+
throw new IllegalArgumentException("sendRedirectHttpStatus should be for redirection. Got " + sendRedirectHttpStatus);
52+
}
53+
this.sendRedirectHttpStatus = sendRedirectHttpStatus;
54+
}
55+
56+
@Override
57+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
58+
filterChain.doFilter(request, new RelativeRedirectResponse(response));
59+
}
60+
61+
/**
62+
* Modifies {@link #sendRedirect(String)} to explicitly set the "Location" header and an HTTP status code to avoid
63+
* containers from rewriting the location to be an absolute URL.
64+
*
65+
* @author Rob Winch
66+
* @since 4.3.10
67+
*/
68+
private class RelativeRedirectResponse extends HttpServletResponseWrapper {
69+
70+
/**
71+
* Constructs a response adaptor wrapping the given response.
72+
*
73+
* @param response
74+
* @throws IllegalArgumentException if the response is null
75+
*/
76+
public RelativeRedirectResponse(HttpServletResponse response) {
77+
super(response);
78+
}
79+
80+
@Override
81+
public void sendRedirect(String location) throws IOException {
82+
setHeader(HttpHeaders.LOCATION, location);
83+
setStatus(sendRedirectHttpStatus.value());
84+
}
85+
}
86+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.filter;
18+
19+
import org.junit.Test;
20+
import org.junit.runner.RunWith;
21+
import org.mockito.InOrder;
22+
import org.mockito.Mock;
23+
import org.mockito.Mockito;
24+
import org.mockito.junit.MockitoJUnitRunner;
25+
import org.springframework.http.HttpHeaders;
26+
import org.springframework.http.HttpStatus;
27+
import org.springframework.mock.web.test.MockFilterChain;
28+
import org.springframework.mock.web.test.MockHttpServletRequest;
29+
30+
import javax.servlet.http.HttpServletResponse;
31+
32+
/**
33+
* @author Rob Winch
34+
* @since 4.3.10
35+
*/
36+
@RunWith(MockitoJUnitRunner.class)
37+
public class RelativeRedirectFilterTests {
38+
@Mock
39+
HttpServletResponse response;
40+
41+
RelativeRedirectFilter filter = new RelativeRedirectFilter();
42+
43+
@Test(expected = IllegalArgumentException.class)
44+
public void sendRedirectHttpStatusWhenNullThenIllegalArgumentException() {
45+
this.filter.setSendRedirectHttpStatus(null);
46+
}
47+
48+
@Test(expected = IllegalArgumentException.class)
49+
public void sendRedirectHttpStatusWhenNot3xxThenIllegalArgumentException() {
50+
this.filter.setSendRedirectHttpStatus(HttpStatus.OK);
51+
}
52+
53+
@Test
54+
public void doFilterSendRedirectWhenDefaultsThenLocationAnd302() throws Exception {
55+
String location = "/foo";
56+
57+
sendRedirect(location);
58+
59+
InOrder inOrder = Mockito.inOrder(this.response);
60+
inOrder.verify(this.response).setHeader(HttpHeaders.LOCATION, location);
61+
inOrder.verify(this.response).setStatus(HttpStatus.FOUND.value());
62+
}
63+
64+
@Test
65+
public void doFilterSendRedirectWhenCustomSendRedirectHttpStatusThenLocationAnd301() throws Exception {
66+
String location = "/foo";
67+
HttpStatus status = HttpStatus.MOVED_PERMANENTLY;
68+
this.filter.setSendRedirectHttpStatus(status);
69+
sendRedirect(location);
70+
71+
InOrder inOrder = Mockito.inOrder(this.response);
72+
inOrder.verify(this.response).setHeader(HttpHeaders.LOCATION, location);
73+
inOrder.verify(this.response).setStatus(status.value());
74+
}
75+
76+
private void sendRedirect(String location) throws Exception {
77+
MockFilterChain chain = new MockFilterChain();
78+
79+
filter.doFilterInternal(new MockHttpServletRequest(), response, chain);
80+
81+
HttpServletResponse wrappedResponse = (HttpServletResponse) chain.getResponse();
82+
wrappedResponse.sendRedirect(location);
83+
}
84+
}

0 commit comments

Comments
 (0)