Skip to content

Commit 9708a2d

Browse files
zeeshanadnaneleftherias
authored andcommitted
Adds cookie based RequestCache
fixes gh-8034
1 parent d3dc8b0 commit 9708a2d

File tree

3 files changed

+334
-2
lines changed

3 files changed

+334
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2002-2020 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+
* https://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+
package org.springframework.security.web.savedrequest;
17+
18+
import org.apache.commons.logging.Log;
19+
import org.apache.commons.logging.LogFactory;
20+
import org.springframework.security.web.util.UrlUtils;
21+
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
22+
import org.springframework.security.web.util.matcher.RequestMatcher;
23+
import org.springframework.util.Assert;
24+
import org.springframework.web.util.UriComponents;
25+
import org.springframework.web.util.UriComponentsBuilder;
26+
import org.springframework.web.util.WebUtils;
27+
28+
import javax.servlet.http.Cookie;
29+
import javax.servlet.http.HttpServletRequest;
30+
import javax.servlet.http.HttpServletResponse;
31+
import java.util.Base64;
32+
33+
34+
/**
35+
* An Implementation of {@code RequestCache} which saves the original request URI in a cookie.
36+
*
37+
* @author Zeeshan Adnan
38+
*/
39+
public class CookieRequestCache implements RequestCache {
40+
41+
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
42+
protected final Log logger = LogFactory.getLog(this.getClass());
43+
44+
private static final String COOKIE_NAME = "REDIRECT_URI";
45+
private static final int COOKIE_MAX_AGE = -1;
46+
47+
@Override
48+
public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
49+
if (requestMatcher.matches(request)) {
50+
String redirectUrl = UrlUtils.buildFullRequestUrl(request);
51+
Cookie savedCookie = new Cookie(COOKIE_NAME, encodeCookie(redirectUrl));
52+
savedCookie.setMaxAge(COOKIE_MAX_AGE);
53+
savedCookie.setSecure(request.isSecure());
54+
savedCookie.setPath(request.getContextPath());
55+
savedCookie.setHttpOnly(true);
56+
57+
response.addCookie(savedCookie);
58+
} else {
59+
logger.debug("Request not saved as configured RequestMatcher did not match");
60+
}
61+
}
62+
63+
@Override
64+
public SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) {
65+
Cookie savedRequestCookie = WebUtils.getCookie(request, COOKIE_NAME);
66+
if (savedRequestCookie != null) {
67+
String originalURI = decodeCookie(savedRequestCookie.getValue());
68+
UriComponents uriComponents = UriComponentsBuilder.fromUriString(originalURI).build();
69+
DefaultSavedRequest.Builder builder = new DefaultSavedRequest.Builder();
70+
71+
int port = uriComponents.getPort();
72+
if (port == -1) {
73+
if ("https".equalsIgnoreCase(uriComponents.getScheme())) {
74+
port = 443;
75+
} else {
76+
port = 80;
77+
}
78+
}
79+
return builder.setScheme(uriComponents.getScheme())
80+
.setServerName(uriComponents.getHost())
81+
.setRequestURI(uriComponents.getPath())
82+
.setQueryString(uriComponents.getQuery())
83+
.setServerPort(port)
84+
.build();
85+
}
86+
return null;
87+
}
88+
89+
@Override
90+
public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) {
91+
SavedRequest savedRequest = getRequest(request, response);
92+
if (savedRequest != null) {
93+
removeRequest(request, response);
94+
return new SavedRequestAwareWrapper(savedRequest, request);
95+
}
96+
return null;
97+
}
98+
99+
@Override
100+
public void removeRequest(HttpServletRequest request, HttpServletResponse response) {
101+
Cookie removeSavedRequestCookie = new Cookie(COOKIE_NAME, "");
102+
removeSavedRequestCookie.setSecure(request.isSecure());
103+
removeSavedRequestCookie.setHttpOnly(true);
104+
removeSavedRequestCookie.setPath(request.getContextPath());
105+
removeSavedRequestCookie.setMaxAge(0);
106+
response.addCookie(removeSavedRequestCookie);
107+
}
108+
109+
private static String encodeCookie(String cookieValue) {
110+
return Base64.getEncoder().encodeToString(cookieValue.getBytes());
111+
}
112+
113+
private static String decodeCookie(String encodedCookieValue) {
114+
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
115+
}
116+
117+
/**
118+
* Allows selective use of saved requests for a subset of requests. By default any
119+
* request will be cached by the {@code saveRequest} method.
120+
* <p>
121+
* If set, only matching requests will be cached.
122+
*
123+
* @param requestMatcher a request matching strategy which defines which requests
124+
* should be cached.
125+
*/
126+
public void setRequestMatcher(RequestMatcher requestMatcher) {
127+
Assert.notNull(requestMatcher, "requestMatcher should not be null");
128+
this.requestMatcher = requestMatcher;
129+
}
130+
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2002-2020 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+
* https://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+
package org.springframework.security.web.savedrequest;
17+
18+
import org.junit.Test;
19+
import org.springframework.mock.web.MockHttpServletRequest;
20+
import org.springframework.mock.web.MockHttpServletResponse;
21+
22+
import javax.servlet.http.Cookie;
23+
import javax.servlet.http.HttpServletRequest;
24+
25+
import java.util.Base64;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
29+
30+
/**
31+
* @author Zeeshan Adnan
32+
*/
33+
public class CookieRequestCacheTests {
34+
35+
private static final String DEFAULT_COOKIE_NAME = "REDIRECT_URI";
36+
37+
@Test
38+
public void saveRequestWhenMatchesThenSavedRequestInACookieOnResponse() {
39+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
40+
41+
MockHttpServletRequest request = requestToSave();
42+
MockHttpServletResponse response = new MockHttpServletResponse();
43+
44+
cookieRequestCache.saveRequest(request, response);
45+
46+
Cookie savedCookie = response.getCookie(DEFAULT_COOKIE_NAME);
47+
assertThat(savedCookie).isNotNull();
48+
49+
String redirectUrl = decodeCookie(savedCookie.getValue());
50+
assertThat(redirectUrl).isEqualTo("https://abc.com/destination?param1=a&param2=b&param3=1122");
51+
52+
assertThat(savedCookie.getMaxAge()).isEqualTo(-1);
53+
assertThat(savedCookie.getPath()).isEqualTo(request.getContextPath());
54+
assertThat(savedCookie.isHttpOnly()).isTrue();
55+
assertThat(savedCookie.getSecure()).isTrue();
56+
57+
}
58+
59+
@Test
60+
public void setRequestMatcherWhenRequestMatcherIsSetNullThenThrowsIllegalArgumentException() {
61+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
62+
assertThatThrownBy(() -> cookieRequestCache.setRequestMatcher(null))
63+
.isInstanceOf(IllegalArgumentException.class);
64+
}
65+
66+
@Test
67+
public void getMatchingRequestWhenRequestMatcherDefinedThenReturnsCorrectSubsetOfCachedRequests() {
68+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
69+
cookieRequestCache.setRequestMatcher(request -> request.getRequestURI().equals("/expected-destination"));
70+
71+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/destination");
72+
MockHttpServletResponse response = new MockHttpServletResponse();
73+
cookieRequestCache.saveRequest(request, response);
74+
75+
SavedRequest savedRequest = cookieRequestCache.getRequest(request, response);
76+
assertThat(savedRequest).isNull();
77+
78+
HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(request, response);
79+
assertThat(matchingRequest).isNull();
80+
}
81+
82+
@Test
83+
public void getRequestWhenRequestIsWithoutCookiesThenReturnsNullSavedRequest() {
84+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
85+
SavedRequest savedRequest = cookieRequestCache.getRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
86+
assertThat(savedRequest).isNull();
87+
}
88+
89+
@Test
90+
public void getRequestWhenRequestDoesNotContainSavedRequestCookieThenReturnsNull() {
91+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
92+
MockHttpServletRequest request = new MockHttpServletRequest();
93+
request.setCookies(new Cookie("abc_cookie", "value"));
94+
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
95+
assertThat(savedRequest).isNull();
96+
}
97+
98+
@Test
99+
public void getRequestWhenRequestContainsSavedRequestCookieThenReturnsSaveRequest() {
100+
101+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
102+
MockHttpServletRequest request = new MockHttpServletRequest();
103+
String redirectUrl = "https://abc.com/destination?param1=a&param2=b&param3=1122";
104+
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
105+
106+
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
107+
assertThat(savedRequest).isNotNull();
108+
assertThat(savedRequest.getRedirectUrl()).isEqualTo(redirectUrl);
109+
}
110+
111+
@Test
112+
public void matchingRequestWhenRequestDoesNotContainSavedRequestCookieThenReturnsNull() {
113+
114+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
115+
MockHttpServletResponse response = new MockHttpServletResponse();
116+
117+
HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(new MockHttpServletRequest(), response);
118+
assertThat(matchingRequest).isNull();
119+
assertThat(response.getCookie(DEFAULT_COOKIE_NAME)).isNull();
120+
121+
}
122+
123+
@Test
124+
public void matchingRequestWhenRequestContainsSavedRequestCookieThenSetsAnExpiredCookieInResponse() {
125+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
126+
MockHttpServletRequest request = new MockHttpServletRequest();
127+
String redirectUrl = "https://abc.com/destination?param1=a&param2=b&param3=1122";
128+
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
129+
MockHttpServletResponse response = new MockHttpServletResponse();
130+
131+
cookieRequestCache.getMatchingRequest(request, response);
132+
Cookie expiredCookie = response.getCookie(DEFAULT_COOKIE_NAME);
133+
assertThat(expiredCookie).isNotNull();
134+
assertThat(expiredCookie.getValue()).isEmpty();
135+
assertThat(expiredCookie.getMaxAge()).isZero();
136+
}
137+
138+
@Test
139+
public void removeRequestWhenInvokedThenSetsAnExpiredCookieOnResponse() {
140+
CookieRequestCache cookieRequestCache = new CookieRequestCache();
141+
MockHttpServletResponse response = new MockHttpServletResponse();
142+
cookieRequestCache.removeRequest(new MockHttpServletRequest(), response);
143+
Cookie expiredCookie = response.getCookie(DEFAULT_COOKIE_NAME);
144+
assertThat(expiredCookie).isNotNull();
145+
assertThat(expiredCookie.getValue()).isEmpty();
146+
assertThat(expiredCookie.getMaxAge()).isZero();
147+
}
148+
149+
private MockHttpServletRequest requestToSave() {
150+
MockHttpServletRequest request = new MockHttpServletRequest();
151+
request.setServerPort(443);
152+
request.setSecure(true);
153+
request.setScheme("https");
154+
request.setServerName("abc.com");
155+
request.setRequestURI("/destination");
156+
request.setQueryString("param1=a&param2=b&param3=1122");
157+
return request;
158+
}
159+
160+
private static String encodeCookie(String cookieValue) {
161+
return Base64.getEncoder().encodeToString(cookieValue.getBytes());
162+
}
163+
164+
private static String decodeCookie(String encodedCookieValue) {
165+
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
166+
}
167+
168+
}

web/src/test/java/org/springframework/security/web/savedrequest/RequestCacheAwareFilterTests.java

+35-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,10 +22,13 @@
2222
import org.springframework.mock.web.MockHttpServletRequest;
2323
import org.springframework.mock.web.MockHttpServletResponse;
2424

25+
import javax.servlet.http.Cookie;
26+
import java.util.Base64;
27+
2528
public class RequestCacheAwareFilterTests {
2629

2730
@Test
28-
public void savedRequestIsRemovedAfterMatch() throws Exception {
31+
public void doFilterWhenHttpSessionRequestCacheConfiguredThenSavedRequestRemovedAfterMatch() throws Exception {
2932
RequestCacheAwareFilter filter = new RequestCacheAwareFilter();
3033
HttpSessionRequestCache cache = new HttpSessionRequestCache();
3134

@@ -40,4 +43,34 @@ public void savedRequestIsRemovedAfterMatch() throws Exception {
4043
assertThat(request.getSession().getAttribute(
4144
HttpSessionRequestCache.SAVED_REQUEST)).isNull();
4245
}
46+
47+
@Test
48+
public void doFilterWhenCookieRequestCacheConfiguredThenExpiredSavedRequestCookieSetAfterMatch() throws Exception {
49+
CookieRequestCache cache = new CookieRequestCache();
50+
RequestCacheAwareFilter filter = new RequestCacheAwareFilter(cache);
51+
52+
MockHttpServletRequest request = new MockHttpServletRequest();
53+
request.setServerName("abc.com");
54+
request.setRequestURI("/destination");
55+
request.setScheme("https");
56+
request.setServerPort(443);
57+
request.setSecure(true);
58+
59+
String encodedRedirectUrl = Base64.getEncoder().encodeToString("https://abc.com/destination".getBytes());
60+
Cookie savedRequest = new Cookie("REDIRECT_URI", encodedRedirectUrl);
61+
savedRequest.setMaxAge(-1);
62+
savedRequest.setSecure(request.isSecure());
63+
savedRequest.setPath("/");
64+
savedRequest.setHttpOnly(true);
65+
request.setCookies(savedRequest);
66+
67+
MockHttpServletResponse response = new MockHttpServletResponse();
68+
69+
filter.doFilter(request, response, new MockFilterChain());
70+
71+
Cookie expiredCookie = response.getCookie("REDIRECT_URI");
72+
assertThat(expiredCookie).isNotNull();
73+
assertThat(expiredCookie.getValue()).isEmpty();
74+
assertThat(expiredCookie.getMaxAge()).isZero();
75+
}
4376
}

0 commit comments

Comments
 (0)