diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java index ca59dd5b05e..3be334a17df 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 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. @@ -145,6 +145,9 @@ private RequestMatcher createDefaultSavedRequestMatcher(H http) { RequestMatcher notFavIcon = new NegatedRequestMatcher(getFaviconRequestMatcher()); RequestMatcher notXRequestedWith = new NegatedRequestMatcher( new RequestHeaderRequestMatcher("X-Requested-With", "XMLHttpRequest")); + RequestMatcher notWebSocket = new NegatedRequestMatcher( + new RequestHeaderRequestMatcher("Upgrade", "websocket")); + boolean isCsrfEnabled = http.getConfigurer(CsrfConfigurer.class) != null; List matchers = new ArrayList<>(); if (isCsrfEnabled) { @@ -156,6 +159,7 @@ private RequestMatcher createDefaultSavedRequestMatcher(H http) { matchers.add(notXRequestedWith); matchers.add(notMatchingMediaType(http, MediaType.MULTIPART_FORM_DATA)); matchers.add(notMatchingMediaType(http, MediaType.TEXT_EVENT_STREAM)); + matchers.add(notWebSocket); return new AndRequestMatcher(matchers); } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java index e6a597e0a8a..bf56cc6e0dd 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/RequestCacheConfigurerTests.java @@ -169,6 +169,23 @@ public void getWhenBookmarkedRequestIsTextEventStreamThenPostAuthenticationRedir this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); } + @Test + public void getWhenBookmarkedRequestIsWebSocketThenPostAuthenticationRedirectsToRoot() throws Exception { + this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire(); + MockHttpServletRequestBuilder request = get("/messages").header("Upgrade", "websocket"); + // @formatter:off + MockHttpSession session = (MockHttpSession) this.mvc.perform(request) + .andExpect(redirectedUrl("http://localhost/login")) + .andReturn() + .getRequest() + .getSession(); + // @formatter:on + // ignores websocket + // This is desirable since websocket requests are typically not invoked + // directly from the browser and we don't want the browser to replay them + this.mvc.perform(formLogin(session)).andExpect(redirectedUrl("/")); + } + @Test public void getWhenBookmarkedRequestIsAllMediaTypeThenPostAuthenticationRemembers() throws Exception { this.spring.register(RequestCacheDefaultsConfig.class, DefaultSecurityConfig.class).autowire();