diff --git a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java index 397900a3c9f..4c05a216f9c 100644 --- a/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java +++ b/web/src/main/java/org/springframework/security/web/session/ConcurrentSessionFilter.java @@ -141,7 +141,7 @@ private void doFilter(HttpServletRequest request, HttpServletResponse response, .of(() -> "Requested session ID " + request.getRequestedSessionId() + " has expired.")); doLogout(request, response); this.sessionInformationExpiredStrategy - .onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response)); + .onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response, chain)); return; } // Non-expired - update last request date/time diff --git a/web/src/main/java/org/springframework/security/web/session/ContinueRequestSessionInformationExpiredStrategy.java b/web/src/main/java/org/springframework/security/web/session/ContinueRequestSessionInformationExpiredStrategy.java new file mode 100644 index 00000000000..ce3f2ca7128 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/session/ContinueRequestSessionInformationExpiredStrategy.java @@ -0,0 +1,36 @@ +/* + * Copyright 2024 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.session; + +import jakarta.servlet.ServletException; +import java.io.IOException; + +/** + * A {@link SessionInformationExpiredStrategy} continues to execute subsequent filters + * even after session expiration. + * + * @author Rosie Yang + * @since 6.3 + */ +public final class ContinueRequestSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy { + + @Override + public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws ServletException, IOException { + event.getFilterChain().doFilter(event.getRequest(), event.getResponse()); + } + +} diff --git a/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java b/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java index 925f5a04400..e64c25ecf05 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java +++ b/web/src/main/java/org/springframework/security/web/session/SessionInformationExpiredEvent.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2016 the original author or authors. + * Copyright 2012-2024 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. @@ -16,6 +16,7 @@ package org.springframework.security.web.session; +import jakarta.servlet.FilterChain; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -27,6 +28,7 @@ * An event for when a {@link SessionInformation} is expired. * * @author Rob Winch + * @author Rosie Yang * @since 4.2 */ public final class SessionInformationExpiredEvent extends ApplicationEvent { @@ -35,6 +37,8 @@ public final class SessionInformationExpiredEvent extends ApplicationEvent { private final HttpServletResponse response; + private final FilterChain filterChain; + /** * Creates a new instance * @param sessionInformation the SessionInformation that is expired @@ -48,6 +52,17 @@ public SessionInformationExpiredEvent(SessionInformation sessionInformation, Htt Assert.notNull(response, "response cannot be null"); this.request = request; this.response = response; + this.filterChain = null; + } + + public SessionInformationExpiredEvent(SessionInformation sessionInformation, HttpServletRequest request, + HttpServletResponse response, FilterChain filterChain) { + super(sessionInformation); + Assert.notNull(request, "request cannot be null"); + Assert.notNull(response, "response cannot be null"); + this.request = request; + this.response = response; + this.filterChain = filterChain; } /** @@ -68,4 +83,7 @@ public SessionInformation getSessionInformation() { return (SessionInformation) getSource(); } + public FilterChain getFilterChain() { + return this.filterChain; + } } diff --git a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java index 32e6702fde7..dc1a3e8b5b9 100644 --- a/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/concurrent/ConcurrentSessionFilterTests.java @@ -39,6 +39,8 @@ import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.security.web.session.ConcurrentSessionFilter; +import org.springframework.security.web.session.ContinueRequestSessionInformationExpiredStrategy; +import org.springframework.security.web.session.SessionInformationExpiredEvent; import org.springframework.security.web.session.SessionInformationExpiredStrategy; import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy; @@ -59,6 +61,7 @@ * @author Ben Alex * @author Luke Taylor * @author Onur Kagan Ozcan + * @author Rosie Yang */ public class ConcurrentSessionFilterTests { @@ -301,4 +304,18 @@ public void setLogoutHandlersWhenEmptyThenThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> filter.setLogoutHandlers(new LogoutHandler[0])); } + @Test + public void doFilterWhenRequestSessionInformationExpiredThenChainIsContinued() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setSession(new MockHttpSession()); + MockHttpServletResponse response = new MockHttpServletResponse(); + RedirectStrategy redirect = mock(RedirectStrategy.class); + SessionRegistry registry = mock(SessionRegistry.class); + ContinueRequestSessionInformationExpiredStrategy expiredStrategy = new ContinueRequestSessionInformationExpiredStrategy(); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredStrategy); + MockFilterChain chain = new MockFilterChain(); + filter.doFilter(request, response, chain); + assertThat(chain.getRequest()).isNotNull(); + } + } diff --git a/web/src/test/java/org/springframework/security/web/session/SessionInformationExpiredEventTests.java b/web/src/test/java/org/springframework/security/web/session/SessionInformationExpiredEventTests.java index 352d6fd9060..8a2527f51e1 100644 --- a/web/src/test/java/org/springframework/security/web/session/SessionInformationExpiredEventTests.java +++ b/web/src/test/java/org/springframework/security/web/session/SessionInformationExpiredEventTests.java @@ -35,19 +35,19 @@ public class SessionInformationExpiredEventTests { @Test public void constructorWhenSessionInformationNullThenThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new SessionInformationExpiredEvent(null, - new MockHttpServletRequest(), new MockHttpServletResponse())); + new MockHttpServletRequest(), new MockHttpServletResponse(), null)); } @Test public void constructorWhenRequestNullThenThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new SessionInformationExpiredEvent( - new SessionInformation("fake", "sessionId", new Date()), null, new MockHttpServletResponse())); + new SessionInformation("fake", "sessionId", new Date()), null, new MockHttpServletResponse(), null)); } @Test public void constructorWhenResponseNullThenThrowsException() { assertThatIllegalArgumentException().isThrownBy(() -> new SessionInformationExpiredEvent( - new SessionInformation("fake", "sessionId", new Date()), new MockHttpServletRequest(), null)); + new SessionInformation("fake", "sessionId", new Date()), new MockHttpServletRequest(), null, null)); } }