diff --git a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java index 01731fed9b..77017338fb 100644 --- a/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/HttpConfigurationBuilder.java @@ -46,6 +46,7 @@ import org.springframework.security.web.savedrequest.RequestCacheAwareFilter; import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter; import org.springframework.security.web.session.ConcurrentSessionFilter; import org.springframework.security.web.session.SessionManagementFilter; +import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; @@ -270,7 +271,7 @@ class HttpConfigurationBuilder { } if (StringUtils.hasText(invalidSessionUrl)) { - sessionMgmtFilter.addPropertyValue("invalidSessionUrl", invalidSessionUrl); + sessionMgmtFilter.addPropertyValue("invalidSessionStrategy", new SimpleRedirectInvalidSessionStrategy(invalidSessionUrl)); } sessionMgmtFilter.addPropertyReference("sessionAuthenticationStrategy", sessionAuthStratRef); diff --git a/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy b/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy index a1019f54e6..9a910a5921 100644 --- a/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/http/SessionManagementConfigTests.groovy @@ -124,7 +124,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests { Object sessionRegistryFromFormLoginFilter = FieldUtils.getFieldValue( getFilter(UsernamePasswordAuthenticationFilter.class),"sessionStrategy.sessionRegistry"); Object sessionRegistryFromMgmtFilter = FieldUtils.getFieldValue( - getFilter(SessionManagementFilter.class),"sessionStrategy.sessionRegistry"); + getFilter(SessionManagementFilter.class),"sessionAuthenticationStrategy.sessionRegistry"); assertSame(sessionRegistry, sessionRegistryFromConcurrencyFilter); assertSame(sessionRegistry, sessionRegistryFromMgmtFilter); @@ -183,7 +183,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests { expect: filter instanceof SessionManagementFilter - filter.invalidSessionUrl == '/timeoutUrl' + filter.invalidSessionStrategy.destinationUrl == '/timeoutUrl' } } diff --git a/docs/manual/src/docbook/appendix-namespace.xml b/docs/manual/src/docbook/appendix-namespace.xml index e6ae087d05..ea5a981cb8 100644 --- a/docs/manual/src/docbook/appendix-namespace.xml +++ b/docs/manual/src/docbook/appendix-namespace.xml @@ -404,6 +404,14 @@ configured DefaultSessionAuthenticationStrategy. See the Javadoc for this class for more details. +
+ <literal>invalid-session-url</literal> + Setting this attribute will inject the SessionManagementFilter + with a SimpleRedirectInvalidSessionStrategy configured with + the attribute value. When an invalid session ID is submitted, the strategy will be invoked, + redirecting to the configured URL. + +
The <literal><concurrency-control></literal> Element diff --git a/docs/manual/src/docbook/session-mgmt.xml b/docs/manual/src/docbook/session-mgmt.xml index 4e305346e7..785f955dbe 100644 --- a/docs/manual/src/docbook/session-mgmt.xml +++ b/docs/manual/src/docbook/session-mgmt.xml @@ -28,10 +28,12 @@ invoke the configured SessionAuthenticationStrategy. If the user is not currently authenticated, the filter will check whether an invalid - session ID has been requested (because of a timeout, for example) and will redirect to - the configured invalidSessionUrl if set. The easiest way to configure - this is through the namespace, as described - earlier. + session ID has been requested (because of a timeout, for example) and will invoke the configured + InvalidSessionStrategy, if one is set. The most common behaviour + is just to redirect to a fixed URL and this is encapsulated in the standard implementation + SimpleRedirectInvalidSessionStrategy. The latter is also used + when configuring an invalid session URL through the namespace, + as described earlier.
<interfacename>SessionAuthenticationStrategy</interfacename> diff --git a/web/src/main/java/org/springframework/security/web/session/InvalidSessionStrategy.java b/web/src/main/java/org/springframework/security/web/session/InvalidSessionStrategy.java new file mode 100644 index 0000000000..e9eaed5882 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/session/InvalidSessionStrategy.java @@ -0,0 +1,18 @@ +package org.springframework.security.web.session; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Determines the behaviour of the {@code SessionManagementFilter} when an invalid session Id is submitted and + * detected in the {@code SessionManagementFilter}. + * + * @author Luke Taylor + */ +public interface InvalidSessionStrategy { + + void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException; + +} diff --git a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java index 55da4d9c2c..d6dd36312c 100644 --- a/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java +++ b/web/src/main/java/org/springframework/security/web/session/SessionManagementFilter.java @@ -13,8 +13,6 @@ import org.springframework.security.authentication.AuthenticationTrustResolver; import org.springframework.security.authentication.AuthenticationTrustResolverImpl; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.DefaultRedirectStrategy; -import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.AuthenticationFailureHandler; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.security.web.authentication.session.SessionAuthenticationException; @@ -41,11 +39,10 @@ public class SessionManagementFilter extends GenericFilterBean { //~ Instance fields ================================================================================================ private final SecurityContextRepository securityContextRepository; - private SessionAuthenticationStrategy sessionStrategy; + private SessionAuthenticationStrategy sessionAuthenticationStrategy; private final AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); - private String invalidSessionUrl; + private InvalidSessionStrategy invalidSessionStrategy = null; private AuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler(); - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); public SessionManagementFilter(SecurityContextRepository securityContextRepository) { this(securityContextRepository, new SessionFixationProtectionStrategy()); @@ -55,7 +52,7 @@ public class SessionManagementFilter extends GenericFilterBean { Assert.notNull(securityContextRepository, "SecurityContextRepository cannot be null"); Assert.notNull(sessionStrategy, "SessionAuthenticationStrategy cannot be null"); this.securityContextRepository = securityContextRepository; - this.sessionStrategy = sessionStrategy; + this.sessionAuthenticationStrategy = sessionStrategy; } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) @@ -76,7 +73,7 @@ public class SessionManagementFilter extends GenericFilterBean { if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) { // The user has been authenticated during the current request, so call the session strategy try { - sessionStrategy.onAuthentication(authentication, request, response); + sessionAuthenticationStrategy.onAuthentication(authentication, request, response); } catch (SessionAuthenticationException e) { // The session strategy can reject the authentication logger.debug("SessionAuthenticationStrategy rejected the authentication object", e); @@ -93,11 +90,8 @@ public class SessionManagementFilter extends GenericFilterBean { if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) { logger.debug("Requested session ID" + request.getRequestedSessionId() + " is invalid."); - if (invalidSessionUrl != null) { - logger.debug("Starting new session (if required) and redirecting to '" + invalidSessionUrl + "'"); - request.getSession(); - redirectStrategy.sendRedirect(request, response, invalidSessionUrl); - + if (invalidSessionStrategy != null) { + invalidSessionStrategy.onInvalidSessionDetected(request, response); return; } } @@ -115,19 +109,19 @@ public class SessionManagementFilter extends GenericFilterBean { * @deprecated Use constructor injection */ @Deprecated - public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionStrategy) { - Assert.notNull(sessionStrategy, "authenticatedSessionStratedy must not be null"); - this.sessionStrategy = sessionStrategy; + public void setSessionAuthenticationStrategy(SessionAuthenticationStrategy sessionAuthenticationStrategy) { + Assert.notNull(sessionAuthenticationStrategy, "authenticatedSessionStratedy must not be null"); + this.sessionAuthenticationStrategy = sessionAuthenticationStrategy; } /** - * Sets the URL to which the response should be redirected if the user agent requests an invalid session Id. - * If the property is not set, no action will be taken. + * Sets the strategy which will be invoked instead of allowing the filter chain to prceed, if the user agent + * requests an invalid session Id. If the property is not set, no action will be taken. * - * @param invalidSessionUrl + * @param invalidSessionStrategy the strategy to invoke. Typically a {@link SimpleRedirectInvalidSessionStrategy}. */ - public void setInvalidSessionUrl(String invalidSessionUrl) { - this.invalidSessionUrl = invalidSessionUrl; + public void setInvalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy) { + this.invalidSessionStrategy = invalidSessionStrategy; } /** @@ -140,8 +134,4 @@ public class SessionManagementFilter extends GenericFilterBean { Assert.notNull(failureHandler, "failureHandler cannot be null"); this.failureHandler = failureHandler; } - - public void setRedirectStrategy(RedirectStrategy redirectStrategy) { - this.redirectStrategy = redirectStrategy; - } } diff --git a/web/src/main/java/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.java b/web/src/main/java/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.java new file mode 100644 index 0000000000..16e5e2d934 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/session/SimpleRedirectInvalidSessionStrategy.java @@ -0,0 +1,48 @@ +package org.springframework.security.web.session; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.security.web.DefaultRedirectStrategy; +import org.springframework.security.web.RedirectStrategy; +import org.springframework.security.web.util.UrlUtils; +import org.springframework.util.Assert; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +/** + * Performs a redirect to a fixed URL when an invalid requested session is detected by the {@code SessionManagementFilter}. + * + * @author Luke Taylor + */ +public final class SimpleRedirectInvalidSessionStrategy implements InvalidSessionStrategy { + private final Log logger = LogFactory.getLog(getClass()); + private final String destinationUrl; + private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + private boolean createNewSession = true; + + public SimpleRedirectInvalidSessionStrategy(String invalidSessionUrl) { + Assert.isTrue(UrlUtils.isValidRedirectUrl(invalidSessionUrl), "url must start with '/' or with 'http(s)'"); + this.destinationUrl = invalidSessionUrl; + } + + public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException { + logger.debug("Starting new session (if required) and redirecting to '" + destinationUrl + "'"); + if (createNewSession) { + request.getSession(); + } + redirectStrategy.sendRedirect(request, response, destinationUrl); + } + + /** + * Determines whether a new session should be created before redirecting (to avoid possible looping issues where + * the same session ID is sent with the redirected request). Alternatively, ensure that the configured URL + * does not pass through the {@code SessionManagementFilter}. + * + * @param createNewSession defaults to {@code true}. + */ + public void setCreateNewSession(boolean createNewSession) { + this.createNewSession = createNewSession; + } +} diff --git a/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java b/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java index d1e7c5943c..ae2fa2c5fe 100644 --- a/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java +++ b/web/src/test/java/org/springframework/security/web/session/SessionManagementFilterTests.java @@ -121,7 +121,6 @@ public class SessionManagementFilterTests { SessionAuthenticationStrategy strategy = mock(SessionAuthenticationStrategy.class); SessionManagementFilter filter = new SessionManagementFilter(repo); filter.setSessionAuthenticationStrategy(strategy); - filter.setRedirectStrategy(new DefaultRedirectStrategy()); MockHttpServletRequest request = new MockHttpServletRequest(); request.setRequestedSessionId("xxx"); request.setRequestedSessionIdValid(false); @@ -134,7 +133,9 @@ public class SessionManagementFilterTests { request = new MockHttpServletRequest(); request.setRequestedSessionId("xxx"); request.setRequestedSessionIdValid(false); - filter.setInvalidSessionUrl("/timedOut"); + SimpleRedirectInvalidSessionStrategy iss = new SimpleRedirectInvalidSessionStrategy("/timedOut"); + iss.setCreateNewSession(true); + filter.setInvalidSessionStrategy(iss); FilterChain fc = mock(FilterChain.class); filter.doFilter(request, response, fc); verifyZeroInteractions(fc);