Ignore Apple touch icon requests in default RequestCache
Safari and other WebKit-based browsers automatically probe for apple-touch-icon.png, apple-touch-icon-precomposed.png, and sized variants (e.g. apple-touch-icon-152x152.png) at the domain root as soon as a page loads, even without a matching <link> tag. This is a background fetch unrelated to what the user actually navigated to, so it should never become the URL a user is redirected to after authenticating, just like the existing favicon.* exclusion. This also refactors the single-pattern favicon matcher into a small list of ignored background-request patterns so more patterns can be added going forward. Closes gh-19692
This commit is contained in:
+21
-4
@@ -32,6 +32,7 @@ import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.web.accept.ContentNegotiationStrategy;
|
||||
@@ -140,7 +141,7 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private RequestMatcher createDefaultSavedRequestMatcher(H http) {
|
||||
RequestMatcher notFavIcon = new NegatedRequestMatcher(getFaviconRequestMatcher());
|
||||
RequestMatcher notIgnoredBackgroundRequest = new NegatedRequestMatcher(getIgnoredBackgroundRequestMatcher());
|
||||
RequestMatcher notXRequestedWith = new NegatedRequestMatcher(
|
||||
new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest"));
|
||||
RequestMatcher notWebSocket = new NegatedRequestMatcher(
|
||||
@@ -152,7 +153,7 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
RequestMatcher getRequests = getRequestMatcherBuilder().matcher(HttpMethod.GET, "/**");
|
||||
matchers.add(0, getRequests);
|
||||
}
|
||||
matchers.add(notFavIcon);
|
||||
matchers.add(notIgnoredBackgroundRequest);
|
||||
matchers.add(notMatchingMediaType(http, MediaType.APPLICATION_JSON));
|
||||
matchers.add(notXRequestedWith);
|
||||
matchers.add(notMatchingMediaType(http, MediaType.MULTIPART_FORM_DATA));
|
||||
@@ -171,8 +172,24 @@ public final class RequestCacheConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
return new NegatedRequestMatcher(mediaRequest);
|
||||
}
|
||||
|
||||
private RequestMatcher getFaviconRequestMatcher() {
|
||||
return getRequestMatcherBuilder().matcher("/favicon.*");
|
||||
/**
|
||||
* Path patterns for requests that browsers request automatically in the background
|
||||
* (e.g. to display an icon), and that should therefore never be saved as the URL to
|
||||
* redirect to after authentication succeeds.
|
||||
*/
|
||||
private static final String[] IGNORED_BACKGROUND_REQUEST_PATTERNS = { "/favicon.*",
|
||||
// Safari/WebKit request apple-touch-icon.png,
|
||||
// apple-touch-icon-precomposed.png,
|
||||
// and sized variants (e.g. apple-touch-icon-152x152.png) even without a
|
||||
// matching <link> tag in the page
|
||||
"/apple-touch-icon*.png" };
|
||||
|
||||
private RequestMatcher getIgnoredBackgroundRequestMatcher() {
|
||||
List<RequestMatcher> matchers = new ArrayList<>();
|
||||
for (String pattern : IGNORED_BACKGROUND_REQUEST_PATTERNS) {
|
||||
matchers.add(getRequestMatcherBuilder().matcher(pattern));
|
||||
}
|
||||
return new OrRequestMatcher(matchers);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+44
@@ -114,6 +114,50 @@ public class RequestCacheConfigurerTests {
|
||||
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedUrlIsAppleTouchIconThenPostAuthenticationRedirectsToRoot() throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/apple-touch-icon.png"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
// @formatter:on
|
||||
// ignores apple-touch-icon.png
|
||||
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedUrlIsAppleTouchIconPrecomposedThenPostAuthenticationRedirectsToRoot()
|
||||
throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/apple-touch-icon-precomposed.png"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
// @formatter:on
|
||||
// ignores apple-touch-icon-precomposed.png
|
||||
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getWhenBookmarkedUrlIsAppleTouchIconSizedPrecomposedThenPostAuthenticationRedirectsToRoot()
|
||||
throws Exception {
|
||||
this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();
|
||||
// @formatter:off
|
||||
MockHttpSession session = (MockHttpSession) this.mvc.perform(get("/apple-touch-icon-152x152-precomposed.png"))
|
||||
.andExpect(redirectedUrl("/login"))
|
||||
.andReturn()
|
||||
.getRequest()
|
||||
.getSession();
|
||||
// @formatter:on
|
||||
// ignores apple-touch-icon-152x152-precomposed.png
|
||||
this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/"));
|
||||
}
|
||||
|
||||
// SEC-2321
|
||||
@Test
|
||||
public void getWhenBookmarkedRequestIsApplicationJsonThenPostAuthenticationRedirectsToRoot() throws Exception {
|
||||
|
||||
+15
-3
@@ -158,13 +158,25 @@ public class CookieServerRequestCache implements ServerRequestCache {
|
||||
StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Path patterns for requests that browsers request automatically in the background
|
||||
* (e.g. to display an icon), and that should therefore never be saved as the URL to
|
||||
* redirect to after authentication succeeds.
|
||||
*/
|
||||
private static final String[] IGNORED_BACKGROUND_REQUEST_PATTERNS = { "/favicon.*",
|
||||
// Safari/WebKit request apple-touch-icon.png,
|
||||
// apple-touch-icon-precomposed.png,
|
||||
// and sized variants (e.g. apple-touch-icon-152x152.png) even without a
|
||||
// matching <link> tag in the page
|
||||
"/apple-touch-icon*.png" };
|
||||
|
||||
private static ServerWebExchangeMatcher createDefaultRequestMatcher() {
|
||||
ServerWebExchangeMatcher get = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/**");
|
||||
ServerWebExchangeMatcher notFavicon = new NegatedServerWebExchangeMatcher(
|
||||
ServerWebExchangeMatchers.pathMatchers("/favicon.*"));
|
||||
ServerWebExchangeMatcher notIgnoredBackgroundRequest = new NegatedServerWebExchangeMatcher(
|
||||
ServerWebExchangeMatchers.pathMatchers(IGNORED_BACKGROUND_REQUEST_PATTERNS));
|
||||
MediaTypeServerWebExchangeMatcher html = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
|
||||
html.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
|
||||
return new AndServerWebExchangeMatcher(get, notFavicon, html);
|
||||
return new AndServerWebExchangeMatcher(get, notIgnoredBackgroundRequest, html);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+15
-3
@@ -158,13 +158,25 @@ public class WebSessionServerRequestCache implements ServerRequestCache {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
/**
|
||||
* Path patterns for requests that browsers request automatically in the background
|
||||
* (e.g. to display an icon), and that should therefore never be saved as the URL to
|
||||
* redirect to after authentication succeeds.
|
||||
*/
|
||||
private static final String[] IGNORED_BACKGROUND_REQUEST_PATTERNS = { "/favicon.*",
|
||||
// Safari/WebKit request apple-touch-icon.png,
|
||||
// apple-touch-icon-precomposed.png,
|
||||
// and sized variants (e.g. apple-touch-icon-152x152.png) even without a
|
||||
// matching <link> tag in the page
|
||||
"/apple-touch-icon*.png" };
|
||||
|
||||
private static ServerWebExchangeMatcher createDefaultRequestMatcher() {
|
||||
ServerWebExchangeMatcher get = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/**");
|
||||
ServerWebExchangeMatcher notFavicon = new NegatedServerWebExchangeMatcher(
|
||||
ServerWebExchangeMatchers.pathMatchers("/favicon.*"));
|
||||
ServerWebExchangeMatcher notIgnoredBackgroundRequest = new NegatedServerWebExchangeMatcher(
|
||||
ServerWebExchangeMatchers.pathMatchers(IGNORED_BACKGROUND_REQUEST_PATTERNS));
|
||||
MediaTypeServerWebExchangeMatcher html = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
|
||||
html.setIgnoredMediaTypes(Collections.singleton(MediaType.ALL));
|
||||
return new AndServerWebExchangeMatcher(get, notFavicon, html);
|
||||
return new AndServerWebExchangeMatcher(get, notIgnoredBackgroundRequest, html);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+27
@@ -77,6 +77,33 @@ public class CookieServerRequestCacheTests {
|
||||
assertThat(cookies).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestWhenGetRequestAppleTouchIconThenNoCookie() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/apple-touch-icon.png").accept(MediaType.TEXT_HTML));
|
||||
this.cache.saveRequest(exchange).block();
|
||||
MultiValueMap<String, ResponseCookie> cookies = exchange.getResponse().getCookies();
|
||||
assertThat(cookies).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestWhenGetRequestAppleTouchIconPrecomposedThenNoCookie() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/apple-touch-icon-precomposed.png").accept(MediaType.TEXT_HTML));
|
||||
this.cache.saveRequest(exchange).block();
|
||||
MultiValueMap<String, ResponseCookie> cookies = exchange.getResponse().getCookies();
|
||||
assertThat(cookies).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestWhenGetRequestAppleTouchIconSizedPrecomposedThenNoCookie() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/apple-touch-icon-152x152-precomposed.png").accept(MediaType.TEXT_HTML));
|
||||
this.cache.saveRequest(exchange).block();
|
||||
MultiValueMap<String, ResponseCookie> cookies = exchange.getResponse().getCookies();
|
||||
assertThat(cookies).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestWhenPostRequestThenNoCookie() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/secured/"));
|
||||
|
||||
+27
@@ -68,6 +68,33 @@ public class WebSessionServerRequestCacheTests {
|
||||
assertThat(saved).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestGetRequestWhenAppleTouchIconThenNotFound() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/apple-touch-icon.png").accept(MediaType.TEXT_HTML));
|
||||
this.cache.saveRequest(exchange).block();
|
||||
URI saved = this.cache.getRedirectUri(exchange).block();
|
||||
assertThat(saved).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestGetRequestWhenAppleTouchIconPrecomposedThenNotFound() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/apple-touch-icon-precomposed.png").accept(MediaType.TEXT_HTML));
|
||||
this.cache.saveRequest(exchange).block();
|
||||
URI saved = this.cache.getRedirectUri(exchange).block();
|
||||
assertThat(saved).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestGetRequestWhenAppleTouchIconSizedPrecomposedThenNotFound() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange
|
||||
.from(MockServerHttpRequest.get("/apple-touch-icon-152x152-precomposed.png").accept(MediaType.TEXT_HTML));
|
||||
this.cache.saveRequest(exchange).block();
|
||||
URI saved = this.cache.getRedirectUri(exchange).block();
|
||||
assertThat(saved).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveRequestGetRequestWhenPostThenNotFound() {
|
||||
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.post("/secured/"));
|
||||
|
||||
Reference in New Issue
Block a user