mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-15 14:53:31 +00:00
Adds cookie based RequestCache
fixes spring-projectsgh-8034
This commit is contained in:
parent
d3dc8b055b
commit
9708a2d63f
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2020 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.security.web.savedrequest;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.security.web.util.UrlUtils;
|
||||||
|
import org.springframework.security.web.util.matcher.AnyRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.util.UriComponents;
|
||||||
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
import org.springframework.web.util.WebUtils;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An Implementation of {@code RequestCache} which saves the original request URI in a cookie.
|
||||||
|
*
|
||||||
|
* @author Zeeshan Adnan
|
||||||
|
*/
|
||||||
|
public class CookieRequestCache implements RequestCache {
|
||||||
|
|
||||||
|
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
|
||||||
|
protected final Log logger = LogFactory.getLog(this.getClass());
|
||||||
|
|
||||||
|
private static final String COOKIE_NAME = "REDIRECT_URI";
|
||||||
|
private static final int COOKIE_MAX_AGE = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
if (requestMatcher.matches(request)) {
|
||||||
|
String redirectUrl = UrlUtils.buildFullRequestUrl(request);
|
||||||
|
Cookie savedCookie = new Cookie(COOKIE_NAME, encodeCookie(redirectUrl));
|
||||||
|
savedCookie.setMaxAge(COOKIE_MAX_AGE);
|
||||||
|
savedCookie.setSecure(request.isSecure());
|
||||||
|
savedCookie.setPath(request.getContextPath());
|
||||||
|
savedCookie.setHttpOnly(true);
|
||||||
|
|
||||||
|
response.addCookie(savedCookie);
|
||||||
|
} else {
|
||||||
|
logger.debug("Request not saved as configured RequestMatcher did not match");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
Cookie savedRequestCookie = WebUtils.getCookie(request, COOKIE_NAME);
|
||||||
|
if (savedRequestCookie != null) {
|
||||||
|
String originalURI = decodeCookie(savedRequestCookie.getValue());
|
||||||
|
UriComponents uriComponents = UriComponentsBuilder.fromUriString(originalURI).build();
|
||||||
|
DefaultSavedRequest.Builder builder = new DefaultSavedRequest.Builder();
|
||||||
|
|
||||||
|
int port = uriComponents.getPort();
|
||||||
|
if (port == -1) {
|
||||||
|
if ("https".equalsIgnoreCase(uriComponents.getScheme())) {
|
||||||
|
port = 443;
|
||||||
|
} else {
|
||||||
|
port = 80;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.setScheme(uriComponents.getScheme())
|
||||||
|
.setServerName(uriComponents.getHost())
|
||||||
|
.setRequestURI(uriComponents.getPath())
|
||||||
|
.setQueryString(uriComponents.getQuery())
|
||||||
|
.setServerPort(port)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
SavedRequest savedRequest = getRequest(request, response);
|
||||||
|
if (savedRequest != null) {
|
||||||
|
removeRequest(request, response);
|
||||||
|
return new SavedRequestAwareWrapper(savedRequest, request);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void removeRequest(HttpServletRequest request, HttpServletResponse response) {
|
||||||
|
Cookie removeSavedRequestCookie = new Cookie(COOKIE_NAME, "");
|
||||||
|
removeSavedRequestCookie.setSecure(request.isSecure());
|
||||||
|
removeSavedRequestCookie.setHttpOnly(true);
|
||||||
|
removeSavedRequestCookie.setPath(request.getContextPath());
|
||||||
|
removeSavedRequestCookie.setMaxAge(0);
|
||||||
|
response.addCookie(removeSavedRequestCookie);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeCookie(String cookieValue) {
|
||||||
|
return Base64.getEncoder().encodeToString(cookieValue.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String decodeCookie(String encodedCookieValue) {
|
||||||
|
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows selective use of saved requests for a subset of requests. By default any
|
||||||
|
* request will be cached by the {@code saveRequest} method.
|
||||||
|
* <p>
|
||||||
|
* If set, only matching requests will be cached.
|
||||||
|
*
|
||||||
|
* @param requestMatcher a request matching strategy which defines which requests
|
||||||
|
* should be cached.
|
||||||
|
*/
|
||||||
|
public void setRequestMatcher(RequestMatcher requestMatcher) {
|
||||||
|
Assert.notNull(requestMatcher, "requestMatcher should not be null");
|
||||||
|
this.requestMatcher = requestMatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2020 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.security.web.savedrequest;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Zeeshan Adnan
|
||||||
|
*/
|
||||||
|
public class CookieRequestCacheTests {
|
||||||
|
|
||||||
|
private static final String DEFAULT_COOKIE_NAME = "REDIRECT_URI";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void saveRequestWhenMatchesThenSavedRequestInACookieOnResponse() {
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
|
||||||
|
MockHttpServletRequest request = requestToSave();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
cookieRequestCache.saveRequest(request, response);
|
||||||
|
|
||||||
|
Cookie savedCookie = response.getCookie(DEFAULT_COOKIE_NAME);
|
||||||
|
assertThat(savedCookie).isNotNull();
|
||||||
|
|
||||||
|
String redirectUrl = decodeCookie(savedCookie.getValue());
|
||||||
|
assertThat(redirectUrl).isEqualTo("https://abc.com/destination?param1=a¶m2=b¶m3=1122");
|
||||||
|
|
||||||
|
assertThat(savedCookie.getMaxAge()).isEqualTo(-1);
|
||||||
|
assertThat(savedCookie.getPath()).isEqualTo(request.getContextPath());
|
||||||
|
assertThat(savedCookie.isHttpOnly()).isTrue();
|
||||||
|
assertThat(savedCookie.getSecure()).isTrue();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setRequestMatcherWhenRequestMatcherIsSetNullThenThrowsIllegalArgumentException() {
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
assertThatThrownBy(() -> cookieRequestCache.setRequestMatcher(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getMatchingRequestWhenRequestMatcherDefinedThenReturnsCorrectSubsetOfCachedRequests() {
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
cookieRequestCache.setRequestMatcher(request -> request.getRequestURI().equals("/expected-destination"));
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/destination");
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
cookieRequestCache.saveRequest(request, response);
|
||||||
|
|
||||||
|
SavedRequest savedRequest = cookieRequestCache.getRequest(request, response);
|
||||||
|
assertThat(savedRequest).isNull();
|
||||||
|
|
||||||
|
HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(request, response);
|
||||||
|
assertThat(matchingRequest).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRequestWhenRequestIsWithoutCookiesThenReturnsNullSavedRequest() {
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
SavedRequest savedRequest = cookieRequestCache.getRequest(new MockHttpServletRequest(), new MockHttpServletResponse());
|
||||||
|
assertThat(savedRequest).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRequestWhenRequestDoesNotContainSavedRequestCookieThenReturnsNull() {
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setCookies(new Cookie("abc_cookie", "value"));
|
||||||
|
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
|
||||||
|
assertThat(savedRequest).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getRequestWhenRequestContainsSavedRequestCookieThenReturnsSaveRequest() {
|
||||||
|
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
String redirectUrl = "https://abc.com/destination?param1=a¶m2=b¶m3=1122";
|
||||||
|
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
|
||||||
|
|
||||||
|
SavedRequest savedRequest = cookieRequestCache.getRequest(request, new MockHttpServletResponse());
|
||||||
|
assertThat(savedRequest).isNotNull();
|
||||||
|
assertThat(savedRequest.getRedirectUrl()).isEqualTo(redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchingRequestWhenRequestDoesNotContainSavedRequestCookieThenReturnsNull() {
|
||||||
|
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(new MockHttpServletRequest(), response);
|
||||||
|
assertThat(matchingRequest).isNull();
|
||||||
|
assertThat(response.getCookie(DEFAULT_COOKIE_NAME)).isNull();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void matchingRequestWhenRequestContainsSavedRequestCookieThenSetsAnExpiredCookieInResponse() {
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
String redirectUrl = "https://abc.com/destination?param1=a¶m2=b¶m3=1122";
|
||||||
|
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
cookieRequestCache.getMatchingRequest(request, response);
|
||||||
|
Cookie expiredCookie = response.getCookie(DEFAULT_COOKIE_NAME);
|
||||||
|
assertThat(expiredCookie).isNotNull();
|
||||||
|
assertThat(expiredCookie.getValue()).isEmpty();
|
||||||
|
assertThat(expiredCookie.getMaxAge()).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeRequestWhenInvokedThenSetsAnExpiredCookieOnResponse() {
|
||||||
|
CookieRequestCache cookieRequestCache = new CookieRequestCache();
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
cookieRequestCache.removeRequest(new MockHttpServletRequest(), response);
|
||||||
|
Cookie expiredCookie = response.getCookie(DEFAULT_COOKIE_NAME);
|
||||||
|
assertThat(expiredCookie).isNotNull();
|
||||||
|
assertThat(expiredCookie.getValue()).isEmpty();
|
||||||
|
assertThat(expiredCookie.getMaxAge()).isZero();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MockHttpServletRequest requestToSave() {
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setServerPort(443);
|
||||||
|
request.setSecure(true);
|
||||||
|
request.setScheme("https");
|
||||||
|
request.setServerName("abc.com");
|
||||||
|
request.setRequestURI("/destination");
|
||||||
|
request.setQueryString("param1=a¶m2=b¶m3=1122");
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeCookie(String cookieValue) {
|
||||||
|
return Base64.getEncoder().encodeToString(cookieValue.getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String decodeCookie(String encodedCookieValue) {
|
||||||
|
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2002-2016 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
@ -22,10 +22,13 @@ import org.springframework.mock.web.MockFilterChain;
|
|||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
import org.springframework.mock.web.MockHttpServletRequest;
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
import org.springframework.mock.web.MockHttpServletResponse;
|
||||||
|
|
||||||
|
import javax.servlet.http.Cookie;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
public class RequestCacheAwareFilterTests {
|
public class RequestCacheAwareFilterTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void savedRequestIsRemovedAfterMatch() throws Exception {
|
public void doFilterWhenHttpSessionRequestCacheConfiguredThenSavedRequestRemovedAfterMatch() throws Exception {
|
||||||
RequestCacheAwareFilter filter = new RequestCacheAwareFilter();
|
RequestCacheAwareFilter filter = new RequestCacheAwareFilter();
|
||||||
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
HttpSessionRequestCache cache = new HttpSessionRequestCache();
|
||||||
|
|
||||||
@ -40,4 +43,34 @@ public class RequestCacheAwareFilterTests {
|
|||||||
assertThat(request.getSession().getAttribute(
|
assertThat(request.getSession().getAttribute(
|
||||||
HttpSessionRequestCache.SAVED_REQUEST)).isNull();
|
HttpSessionRequestCache.SAVED_REQUEST)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenCookieRequestCacheConfiguredThenExpiredSavedRequestCookieSetAfterMatch() throws Exception {
|
||||||
|
CookieRequestCache cache = new CookieRequestCache();
|
||||||
|
RequestCacheAwareFilter filter = new RequestCacheAwareFilter(cache);
|
||||||
|
|
||||||
|
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||||
|
request.setServerName("abc.com");
|
||||||
|
request.setRequestURI("/destination");
|
||||||
|
request.setScheme("https");
|
||||||
|
request.setServerPort(443);
|
||||||
|
request.setSecure(true);
|
||||||
|
|
||||||
|
String encodedRedirectUrl = Base64.getEncoder().encodeToString("https://abc.com/destination".getBytes());
|
||||||
|
Cookie savedRequest = new Cookie("REDIRECT_URI", encodedRedirectUrl);
|
||||||
|
savedRequest.setMaxAge(-1);
|
||||||
|
savedRequest.setSecure(request.isSecure());
|
||||||
|
savedRequest.setPath("/");
|
||||||
|
savedRequest.setHttpOnly(true);
|
||||||
|
request.setCookies(savedRequest);
|
||||||
|
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
filter.doFilter(request, response, new MockFilterChain());
|
||||||
|
|
||||||
|
Cookie expiredCookie = response.getCookie("REDIRECT_URI");
|
||||||
|
assertThat(expiredCookie).isNotNull();
|
||||||
|
assertThat(expiredCookie.getValue()).isEmpty();
|
||||||
|
assertThat(expiredCookie.getMaxAge()).isZero();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user