Improve request matching logic when using cookie

- Repair request cache deleted by mistake
- Fix RequestCache throw exception and error redirect.

Closes gh-8820
Closes gh-8817
This commit is contained in:
majian 2020-07-08 18:38:22 +08:00 committed by Eleftheria Stein
parent 97ccbe5df2
commit 41f26b768a
2 changed files with 60 additions and 10 deletions

View File

@ -21,6 +21,8 @@ import org.springframework.security.web.util.UrlUtils;
import org.springframework.security.web.util.matcher.AnyRequestMatcher; import org.springframework.security.web.util.matcher.AnyRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents; import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.WebUtils; import org.springframework.web.util.WebUtils;
@ -29,6 +31,7 @@ import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap;
/** /**
@ -52,7 +55,7 @@ public class CookieRequestCache implements RequestCache {
Cookie savedCookie = new Cookie(COOKIE_NAME, encodeCookie(redirectUrl)); Cookie savedCookie = new Cookie(COOKIE_NAME, encodeCookie(redirectUrl));
savedCookie.setMaxAge(COOKIE_MAX_AGE); savedCookie.setMaxAge(COOKIE_MAX_AGE);
savedCookie.setSecure(request.isSecure()); savedCookie.setSecure(request.isSecure());
savedCookie.setPath(request.getContextPath()); savedCookie.setPath(getCookiePath(request));
savedCookie.setHttpOnly(true); savedCookie.setHttpOnly(true);
response.addCookie(savedCookie); response.addCookie(savedCookie);
@ -65,7 +68,7 @@ public class CookieRequestCache implements RequestCache {
public SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) { public SavedRequest getRequest(HttpServletRequest request, HttpServletResponse response) {
Cookie savedRequestCookie = WebUtils.getCookie(request, COOKIE_NAME); Cookie savedRequestCookie = WebUtils.getCookie(request, COOKIE_NAME);
if (savedRequestCookie != null) { if (savedRequestCookie != null) {
String originalURI = decodeCookie(savedRequestCookie.getValue()); final String originalURI = decodeCookie(savedRequestCookie.getValue());
UriComponents uriComponents = UriComponentsBuilder.fromUriString(originalURI).build(); UriComponents uriComponents = UriComponentsBuilder.fromUriString(originalURI).build();
DefaultSavedRequest.Builder builder = new DefaultSavedRequest.Builder(); DefaultSavedRequest.Builder builder = new DefaultSavedRequest.Builder();
@ -77,11 +80,21 @@ public class CookieRequestCache implements RequestCache {
port = 80; port = 80;
} }
} }
final MultiValueMap<String, String> queryParams = uriComponents.getQueryParams();
if (!queryParams.isEmpty()) {
final HashMap<String, String[]> parameters = new HashMap<>(queryParams.size());
queryParams.forEach((key, value) -> parameters.put(key, value.toArray(new String[]{})));
builder.setParameters(parameters);
}
return builder.setScheme(uriComponents.getScheme()) return builder.setScheme(uriComponents.getScheme())
.setServerName(uriComponents.getHost()) .setServerName(uriComponents.getHost())
.setRequestURI(uriComponents.getPath()) .setRequestURI(uriComponents.getPath())
.setQueryString(uriComponents.getQuery()) .setQueryString(uriComponents.getQuery())
.setServerPort(port) .setServerPort(port)
.setMethod(request.getMethod())
.build(); .build();
} }
return null; return null;
@ -89,12 +102,14 @@ public class CookieRequestCache implements RequestCache {
@Override @Override
public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) { public HttpServletRequest getMatchingRequest(HttpServletRequest request, HttpServletResponse response) {
SavedRequest savedRequest = getRequest(request, response); SavedRequest saved = this.getRequest(request, response);
if (savedRequest != null) { if (!this.matchesSavedRequest(request, saved)) {
removeRequest(request, response); this.logger.debug("saved request doesn't match");
return new SavedRequestAwareWrapper(savedRequest, request);
}
return null; return null;
} else {
this.removeRequest(request, response);
return new SavedRequestAwareWrapper(saved, request);
}
} }
@Override @Override
@ -102,7 +117,7 @@ public class CookieRequestCache implements RequestCache {
Cookie removeSavedRequestCookie = new Cookie(COOKIE_NAME, ""); Cookie removeSavedRequestCookie = new Cookie(COOKIE_NAME, "");
removeSavedRequestCookie.setSecure(request.isSecure()); removeSavedRequestCookie.setSecure(request.isSecure());
removeSavedRequestCookie.setHttpOnly(true); removeSavedRequestCookie.setHttpOnly(true);
removeSavedRequestCookie.setPath(request.getContextPath()); removeSavedRequestCookie.setPath(getCookiePath(request));
removeSavedRequestCookie.setMaxAge(0); removeSavedRequestCookie.setMaxAge(0);
response.addCookie(removeSavedRequestCookie); response.addCookie(removeSavedRequestCookie);
} }
@ -115,6 +130,23 @@ public class CookieRequestCache implements RequestCache {
return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes())); return new String(Base64.getDecoder().decode(encodedCookieValue.getBytes()));
} }
private static String getCookiePath(HttpServletRequest request) {
final String contextPath = request.getContextPath();
if (StringUtils.isEmpty(contextPath)) {
return "/";
}
return contextPath;
}
private boolean matchesSavedRequest(HttpServletRequest request, SavedRequest savedRequest) {
if (savedRequest == null) {
return false;
} else {
String currentUrl = UrlUtils.buildFullRequestUrl(request);
return savedRequest.getRedirectUrl().equals(currentUrl);
}
}
/** /**
* Allows selective use of saved requests for a subset of requests. By default any * Allows selective use of saved requests for a subset of requests. By default any
* request will be cached by the {@code saveRequest} method. * request will be cached by the {@code saveRequest} method.

View File

@ -18,6 +18,7 @@ package org.springframework.security.web.savedrequest;
import org.junit.Test; import org.junit.Test;
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 org.springframework.util.StringUtils;
import javax.servlet.http.Cookie; import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@ -50,7 +51,7 @@ public class CookieRequestCacheTests {
assertThat(redirectUrl).isEqualTo("https://abc.com/destination?param1=a&param2=b&param3=1122"); assertThat(redirectUrl).isEqualTo("https://abc.com/destination?param1=a&param2=b&param3=1122");
assertThat(savedCookie.getMaxAge()).isEqualTo(-1); assertThat(savedCookie.getMaxAge()).isEqualTo(-1);
assertThat(savedCookie.getPath()).isEqualTo(request.getContextPath()); assertThat(savedCookie.getPath()).isEqualTo(StringUtils.isEmpty(request.getContextPath()) ? "/" : request.getContextPath());
assertThat(savedCookie.isHttpOnly()).isTrue(); assertThat(savedCookie.isHttpOnly()).isTrue();
assertThat(savedCookie.getSecure()).isTrue(); assertThat(savedCookie.getSecure()).isTrue();
@ -123,7 +124,8 @@ public class CookieRequestCacheTests {
@Test @Test
public void matchingRequestWhenRequestContainsSavedRequestCookieThenSetsAnExpiredCookieInResponse() { public void matchingRequestWhenRequestContainsSavedRequestCookieThenSetsAnExpiredCookieInResponse() {
CookieRequestCache cookieRequestCache = new CookieRequestCache(); CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = requestToSave();
String redirectUrl = "https://abc.com/destination?param1=a&param2=b&param3=1122"; String redirectUrl = "https://abc.com/destination?param1=a&param2=b&param3=1122";
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl))); request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
@ -135,6 +137,22 @@ public class CookieRequestCacheTests {
assertThat(expiredCookie.getMaxAge()).isZero(); assertThat(expiredCookie.getMaxAge()).isZero();
} }
@Test
public void notMatchingRequestWhenRequestNotContainsSavedRequestCookie() {
CookieRequestCache cookieRequestCache = new CookieRequestCache();
MockHttpServletRequest request = requestToSave();
String redirectUrl = "https://abc.com/api";
request.setCookies(new Cookie(DEFAULT_COOKIE_NAME, encodeCookie(redirectUrl)));
MockHttpServletResponse response = new MockHttpServletResponse();
final HttpServletRequest matchingRequest = cookieRequestCache.getMatchingRequest(request, response);
assertThat(matchingRequest).isNull();
Cookie expiredCookie = response.getCookie(DEFAULT_COOKIE_NAME);
assertThat(expiredCookie).isNull();
}
@Test @Test
public void removeRequestWhenInvokedThenSetsAnExpiredCookieOnResponse() { public void removeRequestWhenInvokedThenSetsAnExpiredCookieOnResponse() {
CookieRequestCache cookieRequestCache = new CookieRequestCache(); CookieRequestCache cookieRequestCache = new CookieRequestCache();