diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java index 72353eb88a..0043a8e9a9 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/SessionManagementConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2016 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. @@ -47,8 +47,10 @@ import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.session.ConcurrentSessionFilter; +import org.springframework.security.web.session.ExpiredSessionStrategy; import org.springframework.security.web.session.InvalidSessionStrategy; import org.springframework.security.web.session.SessionManagementFilter; +import org.springframework.security.web.session.SimpleRedirectExpiredSessionStrategy; import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.util.Assert; @@ -89,13 +91,14 @@ import org.springframework.util.Assert; * @see SessionManagementFilter * @see ConcurrentSessionFilter */ -public final class SessionManagementConfigurer> extends - AbstractHttpConfigurer, H> { +public final class SessionManagementConfigurer> + extends AbstractHttpConfigurer, H> { private final SessionAuthenticationStrategy DEFAULT_SESSION_FIXATION_STRATEGY = createDefaultSessionFixationProtectionStrategy(); - private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = DEFAULT_SESSION_FIXATION_STRATEGY; + private SessionAuthenticationStrategy sessionFixationAuthenticationStrategy = this.DEFAULT_SESSION_FIXATION_STRATEGY; private SessionAuthenticationStrategy sessionAuthenticationStrategy; private SessionAuthenticationStrategy providedSessionAuthenticationStrategy; private InvalidSessionStrategy invalidSessionStrategy; + private ExpiredSessionStrategy expiredSessionStrategy; private List sessionAuthenticationStrategies = new ArrayList(); private SessionRegistry sessionRegistry; private Integer maximumSessions; @@ -131,10 +134,12 @@ public final class SessionManagementConfigurer> * Setting this attribute will inject the provided invalidSessionStrategy into the * {@link SessionManagementFilter}. When an invalid session ID is submitted, the * strategy will be invoked, redirecting to the configured URL. - * @param invalidSessionStrategy the strategy to use when an invalid session ID is submitted. + * @param invalidSessionStrategy the strategy to use when an invalid session ID is + * submitted. * @return the {@link SessionManagementConfigurer} for further customization */ - public SessionManagementConfigurer invalidSessionStrategy(InvalidSessionStrategy invalidSessionStrategy) { + public SessionManagementConfigurer invalidSessionStrategy( + InvalidSessionStrategy invalidSessionStrategy) { Assert.notNull(invalidSessionStrategy, "invalidSessionStrategy"); this.invalidSessionStrategy = invalidSessionStrategy; return this; @@ -198,7 +203,8 @@ public final class SessionManagementConfigurer> * {@link SessionAuthenticationStrategy} the supplied sessionAuthenticationStrategy, * {@link RegisterSessionAuthenticationStrategy}. * - * NOTE: Supplying a custom {@link SessionAuthenticationStrategy} will override the default provided {@link SessionFixationProtectionStrategy}. + * NOTE: Supplying a custom {@link SessionAuthenticationStrategy} will override the + * default provided {@link SessionFixationProtectionStrategy}. * * @param sessionAuthenticationStrategy * @return the {@link SessionManagementConfigurer} for further customizations @@ -244,7 +250,8 @@ public final class SessionManagementConfigurer> */ private void setSessionFixationAuthenticationStrategy( SessionAuthenticationStrategy sessionFixationAuthenticationStrategy) { - this.sessionFixationAuthenticationStrategy = postProcess(sessionFixationAuthenticationStrategy); + this.sessionFixationAuthenticationStrategy = postProcess( + sessionFixationAuthenticationStrategy); } /** @@ -273,7 +280,8 @@ public final class SessionManagementConfigurer> * @return the {@link SessionManagementConfigurer} for further customizations */ public SessionManagementConfigurer migrateSession() { - setSessionFixationAuthenticationStrategy(new SessionFixationProtectionStrategy()); + setSessionFixationAuthenticationStrategy( + new SessionFixationProtectionStrategy()); return SessionManagementConfigurer.this; } @@ -288,7 +296,8 @@ public final class SessionManagementConfigurer> * @throws IllegalStateException if the container is not Servlet 3.1 or newer. */ public SessionManagementConfigurer changeSessionId() { - setSessionFixationAuthenticationStrategy(new ChangeSessionIdAuthenticationStrategy()); + setSessionFixationAuthenticationStrategy( + new ChangeSessionIdAuthenticationStrategy()); return SessionManagementConfigurer.this; } @@ -301,7 +310,8 @@ public final class SessionManagementConfigurer> * @return the {@link SessionManagementConfigurer} for further customizations */ public SessionManagementConfigurer none() { - setSessionFixationAuthenticationStrategy(new NullAuthenticatedSessionStrategy()); + setSessionFixationAuthenticationStrategy( + new NullAuthenticatedSessionStrategy()); return SessionManagementConfigurer.this; } } @@ -326,6 +336,12 @@ public final class SessionManagementConfigurer> return this; } + public ConcurrencyControlConfigurer expiredSessionStrategy( + ExpiredSessionStrategy expiredSessionStrategy) { + SessionManagementConfigurer.this.expiredSessionStrategy = expiredSessionStrategy; + return this; + } + /** * If true, prevents a user from authenticating when the * {@link #maximumSessions(int)} has been reached. Otherwise (default), the user @@ -384,7 +400,8 @@ public final class SessionManagementConfigurer> } else { HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository(); - httpSecurityRepository.setDisableUrlRewriting(!enableSessionUrlRewriting); + httpSecurityRepository + .setDisableUrlRewriting(!this.enableSessionUrlRewriting); httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation()); AuthenticationTrustResolver trustResolver = http .getSharedObject(AuthenticationTrustResolver.class); @@ -413,15 +430,14 @@ public final class SessionManagementConfigurer> .getSharedObject(SecurityContextRepository.class); SessionManagementFilter sessionManagementFilter = new SessionManagementFilter( securityContextRepository, getSessionAuthenticationStrategy(http)); - if (sessionAuthenticationErrorUrl != null) { - sessionManagementFilter - .setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler( - sessionAuthenticationErrorUrl)); + if (this.sessionAuthenticationErrorUrl != null) { + sessionManagementFilter.setAuthenticationFailureHandler( + new SimpleUrlAuthenticationFailureHandler( + this.sessionAuthenticationErrorUrl)); } InvalidSessionStrategy strategy = getInvalidSessionStrategy(); if (strategy != null) { - sessionManagementFilter - .setInvalidSessionStrategy(strategy); + sessionManagementFilter.setInvalidSessionStrategy(strategy); } AuthenticationTrustResolver trustResolver = http .getSharedObject(AuthenticationTrustResolver.class); @@ -433,7 +449,10 @@ public final class SessionManagementConfigurer> http.addFilter(sessionManagementFilter); if (isConcurrentSessionControlEnabled()) { ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter( - getSessionRegistry(http), expiredUrl); + getSessionRegistry(http)); + concurrentSessionFilter + .setExpiredSessionStrategy(getExpiredSessionStrategy()); + concurrentSessionFilter = postProcess(concurrentSessionFilter); http.addFilter(concurrentSessionFilter); } @@ -447,14 +466,30 @@ public final class SessionManagementConfigurer> * @return the {@link InvalidSessionStrategy} to use */ InvalidSessionStrategy getInvalidSessionStrategy() { - if(invalidSessionStrategy != null) { - return invalidSessionStrategy; + if (this.invalidSessionStrategy != null) { + return this.invalidSessionStrategy; } - if (invalidSessionUrl != null) { - invalidSessionStrategy = new SimpleRedirectInvalidSessionStrategy( - invalidSessionUrl); + if (this.invalidSessionUrl != null) { + this.invalidSessionStrategy = new SimpleRedirectInvalidSessionStrategy( + this.invalidSessionUrl); } - return invalidSessionStrategy; + return this.invalidSessionStrategy; + } + + ExpiredSessionStrategy getExpiredSessionStrategy() { + if (this.expiredSessionStrategy != null) { + return this.expiredSessionStrategy; + } + + if (this.expiredUrl == null) { + return null; + } + + if (this.expiredSessionStrategy == null) { + this.expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy( + this.expiredUrl); + } + return this.expiredSessionStrategy; } /** @@ -462,7 +497,7 @@ public final class SessionManagementConfigurer> * @return the {@link SessionCreationPolicy} */ SessionCreationPolicy getSessionCreationPolicy() { - return sessionPolicy; + return this.sessionPolicy; } /** @@ -471,8 +506,8 @@ public final class SessionManagementConfigurer> * @return true if the {@link SessionCreationPolicy} allows session creation */ private boolean isAllowSessionCreation() { - return SessionCreationPolicy.ALWAYS == sessionPolicy - || SessionCreationPolicy.IF_REQUIRED == sessionPolicy; + return SessionCreationPolicy.ALWAYS == this.sessionPolicy + || SessionCreationPolicy.IF_REQUIRED == this.sessionPolicy; } /** @@ -480,7 +515,7 @@ public final class SessionManagementConfigurer> * @return */ private boolean isStateless() { - return SessionCreationPolicy.STATELESS == sessionPolicy; + return SessionCreationPolicy.STATELESS == this.sessionPolicy; } /** @@ -491,50 +526,52 @@ public final class SessionManagementConfigurer> * @return the {@link SessionAuthenticationStrategy} to use */ private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) { - if (sessionAuthenticationStrategy != null) { - return sessionAuthenticationStrategy; + if (this.sessionAuthenticationStrategy != null) { + return this.sessionAuthenticationStrategy; } - List delegateStrategies = sessionAuthenticationStrategies; + List delegateStrategies = this.sessionAuthenticationStrategies; SessionAuthenticationStrategy defaultSessionAuthenticationStrategy; - if (providedSessionAuthenticationStrategy == null) { + if (this.providedSessionAuthenticationStrategy == null) { // If a user provided SessionAuthenticationStrategy is not supplied // then default to SessionFixationProtectionStrategy - defaultSessionAuthenticationStrategy = postProcess(sessionFixationAuthenticationStrategy); - } else { - defaultSessionAuthenticationStrategy = providedSessionAuthenticationStrategy; + defaultSessionAuthenticationStrategy = postProcess( + this.sessionFixationAuthenticationStrategy); + } + else { + defaultSessionAuthenticationStrategy = this.providedSessionAuthenticationStrategy; } if (isConcurrentSessionControlEnabled()) { SessionRegistry sessionRegistry = getSessionRegistry(http); ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy( sessionRegistry); - concurrentSessionControlStrategy.setMaximumSessions(maximumSessions); + concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions); concurrentSessionControlStrategy - .setExceptionIfMaximumExceeded(maxSessionsPreventsLogin); - concurrentSessionControlStrategy = postProcess(concurrentSessionControlStrategy); + .setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin); + concurrentSessionControlStrategy = postProcess( + concurrentSessionControlStrategy); RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy( sessionRegistry); registerSessionStrategy = postProcess(registerSessionStrategy); - delegateStrategies.addAll(Arrays.asList( - concurrentSessionControlStrategy, - defaultSessionAuthenticationStrategy, - registerSessionStrategy)); - } else { + delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy, + defaultSessionAuthenticationStrategy, registerSessionStrategy)); + } + else { delegateStrategies.add(defaultSessionAuthenticationStrategy); } - sessionAuthenticationStrategy = postProcess(new CompositeSessionAuthenticationStrategy( - delegateStrategies)); - return sessionAuthenticationStrategy; + this.sessionAuthenticationStrategy = postProcess( + new CompositeSessionAuthenticationStrategy(delegateStrategies)); + return this.sessionAuthenticationStrategy; } private SessionRegistry getSessionRegistry(H http) { - if (sessionRegistry == null) { + if (this.sessionRegistry == null) { SessionRegistryImpl sessionRegistry = new SessionRegistryImpl(); registerDelegateApplicationListener(http, sessionRegistry); this.sessionRegistry = sessionRegistry; } - return sessionRegistry; + return this.sessionRegistry; } private void registerDelegateApplicationListener(H http, @@ -558,7 +595,7 @@ public final class SessionManagementConfigurer> * @return */ private boolean isConcurrentSessionControlEnabled() { - return maximumSessions != null; + return this.maximumSessions != null; } /** 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 9a75f8d8d5..c2e42e205b 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 @@ -15,13 +15,9 @@ */ package org.springframework.security.config.http; -import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.*; -import static org.springframework.security.config.http.SecurityFilters.*; - import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; - import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; @@ -69,6 +65,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.SimpleRedirectExpiredSessionStrategy; import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.util.ClassUtils; @@ -77,6 +74,9 @@ import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; import org.w3c.dom.Element; +import static org.springframework.security.config.http.HttpSecurityBeanDefinitionParser.*; +import static org.springframework.security.config.http.SecurityFilters.*; + /** * Stateful class which helps HttpSecurityBDP to create the configuration for the * <http> element. @@ -97,7 +97,7 @@ class HttpConfigurationBuilder { private static final String ATT_SESSION_AUTH_STRATEGY_REF = "session-authentication-strategy-ref"; private static final String ATT_SESSION_AUTH_ERROR_URL = "session-authentication-error-url"; private static final String ATT_SECURITY_CONTEXT_REPOSITORY = "security-context-repository-ref"; - + private static final String ATT_INVALID_SESSION_STRATEGY_REF = "invalid-session-strategy-ref"; private static final String ATT_DISABLE_URL_REWRITING = "disable-url-rewriting"; private static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; @@ -289,6 +289,7 @@ class HttpConfigurationBuilder { String sessionFixationAttribute = null; String invalidSessionUrl = null; + String invalidSessionStrategyRef = null; String sessionAuthStratRef = null; String errorUrl = null; @@ -304,6 +305,8 @@ class HttpConfigurationBuilder { sessionFixationAttribute = sessionMgmtElt .getAttribute(ATT_SESSION_FIXATION_PROTECTION); invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL); + invalidSessionStrategyRef = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_STRATEGY_REF); + sessionAuthStratRef = sessionMgmtElt .getAttribute(ATT_SESSION_AUTH_STRATEGY_REF); errorUrl = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_ERROR_URL); @@ -311,6 +314,11 @@ class HttpConfigurationBuilder { Elements.CONCURRENT_SESSIONS); sessionControlEnabled = sessionCtrlElt != null; + if (StringUtils.hasText(invalidSessionUrl) && StringUtils.hasText(invalidSessionStrategyRef)) { + pc.getReaderContext().error(ATT_INVALID_SESSION_URL + " attribute cannot be used in combination with" + + " the " + ATT_INVALID_SESSION_STRATEGY_REF + " attribute.", sessionMgmtElt); + } + if (sessionControlEnabled) { if (StringUtils.hasText(sessionAuthStratRef)) { pc.getReaderContext().error( @@ -438,12 +446,16 @@ class HttpConfigurationBuilder { } + + if (StringUtils.hasText(invalidSessionUrl)) { BeanDefinitionBuilder invalidSessionBldr = BeanDefinitionBuilder .rootBeanDefinition(SimpleRedirectInvalidSessionStrategy.class); invalidSessionBldr.addConstructorArgValue(invalidSessionUrl); invalidSession = invalidSessionBldr.getBeanDefinition(); sessionMgmtFilter.addPropertyValue("invalidSessionStrategy", invalidSession); + } else if (StringUtils.hasText(invalidSessionStrategyRef)) { + sessionMgmtFilter.addPropertyReference("invalidSessionStrategy", invalidSessionStrategyRef); } sessionMgmtFilter.addConstructorArgReference(sessionAuthStratRef); @@ -454,6 +466,7 @@ class HttpConfigurationBuilder { private void createConcurrencyControlFilterAndSessionRegistry(Element element) { final String ATT_EXPIRY_URL = "expired-url"; + final String ATT_EXPIRED_SESSION_STRATEGY_REF = "expired-session-strategy-ref"; final String ATT_SESSION_REGISTRY_ALIAS = "session-registry-alias"; final String ATT_SESSION_REGISTRY_REF = "session-registry-ref"; @@ -489,10 +502,20 @@ class HttpConfigurationBuilder { filterBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); String expiryUrl = element.getAttribute(ATT_EXPIRY_URL); + String expiredSessionStrategyRef = element.getAttribute(ATT_EXPIRED_SESSION_STRATEGY_REF); + + if (StringUtils.hasText(expiryUrl) && StringUtils.hasText(expiredSessionStrategyRef)) { + pc.getReaderContext().error("Cannot use 'expired-url' attribute and 'expired-session-strategy-ref'" + + " attribute together.", source); + } if (StringUtils.hasText(expiryUrl)) { - WebConfigUtils.validateHttpRedirect(expiryUrl, pc, source); - filterBuilder.addConstructorArgValue(expiryUrl); + BeanDefinitionBuilder expiredSessionBldr = BeanDefinitionBuilder + .rootBeanDefinition(SimpleRedirectExpiredSessionStrategy.class); + expiredSessionBldr.addConstructorArgValue(expiryUrl); + filterBuilder.addPropertyValue("expiredSessionStrategy", expiredSessionBldr.getBeanDefinition()); + } else if (StringUtils.hasText(expiredSessionStrategyRef)) { + filterBuilder.addPropertyReference("expiredSessionStrategy", expiredSessionStrategyRef); } pc.popAndRegisterContainingComponent(); diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc index 5ce746a2eb..899ac3ff95 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.rnc @@ -535,6 +535,9 @@ session-management.attlist &= session-management.attlist &= ## The URL to which a user will be redirected if they submit an invalid session indentifier. Typically used to detect session timeouts. attribute invalid-session-url {xsd:token}? +session-management.attlist &= + ## Allows injection of the InvalidSessionStrategy instance used by the SessionManagementFilter + attribute invalid-session-strategy-ref {xsd:token}? session-management.attlist &= ## Allows injection of the SessionAuthenticationStrategy instance used by the SessionManagementFilter attribute session-authentication-strategy-ref {xsd:token}? @@ -553,6 +556,9 @@ concurrency-control.attlist &= concurrency-control.attlist &= ## The URL a user will be redirected to if they attempt to use a session which has been "expired" because they have logged in again. attribute expired-url {xsd:token}? +concurrency-control.attlist &= + ## Allows injection of the ExpiredSessionStrategy instance used by the ConcurrentSessionFilter + attribute expired-session-strategy-ref {xsd:token}? concurrency-control.attlist &= ## Specifies that an unauthorized error should be reported when a user attempts to login when they already have the maximum configured sessions open. The default behaviour is to expire the original session. If the session-authentication-error-url attribute is set on the session-management URL, the user will be redirected to this URL. attribute error-if-maximum-exceeded {xsd:boolean}? diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd index 4175b9599a..5dcee117f1 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-4.1.xsd @@ -1743,6 +1743,13 @@ + + + Allows injection of the InvalidSessionStrategy instance used by the + SessionManagementFilter + + + Allows injection of the SessionAuthenticationStrategy instance used by the @@ -1777,6 +1784,13 @@ + + + Allows injection of the ExpiredSessionStrategy instance used by the + ConcurrentSessionFilter + + + Specifies that an unauthorized error should be reported when a user attempts to login when diff --git a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.groovy b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.groovy index 923fb08b1d..b95db13253 100644 --- a/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.groovy +++ b/config/src/test/groovy/org/springframework/security/config/annotation/web/configurers/NamespaceSessionManagementTests.groovy @@ -67,7 +67,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec { concurrentStrategy.maximumSessions == 1 concurrentStrategy.exceptionIfMaximumExceeded concurrentStrategy.sessionRegistry == CustomSessionManagementConfig.SR - findFilter(ConcurrentSessionFilter).expiredUrl == "/expired-session" + findFilter(ConcurrentSessionFilter).expiredSessionStrategy.destinationUrl == "/expired-session" } @EnableWebSecurity 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 0f0e98df6b..9b5b00ba14 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 @@ -15,13 +15,6 @@ */ package org.springframework.security.config.http -import static org.junit.Assert.assertSame -import static org.mockito.Mockito.* - -import javax.servlet.http.HttpServletRequest -import javax.servlet.http.HttpServletResponse - -import org.mockito.Mockito import org.springframework.mock.web.MockFilterChain import org.springframework.mock.web.MockHttpServletRequest import org.springframework.mock.web.MockHttpServletResponse @@ -41,7 +34,6 @@ import org.springframework.security.web.authentication.logout.CookieClearingLogo import org.springframework.security.web.authentication.logout.LogoutFilter import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler import org.springframework.security.web.authentication.rememberme.RememberMeAuthenticationFilter -import org.springframework.security.web.authentication.session.SessionAuthenticationException import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy import org.springframework.security.web.context.NullSecurityContextRepository import org.springframework.security.web.context.SaveContextOnUpdateOrErrorResponseWrapper @@ -50,6 +42,13 @@ import org.springframework.security.web.savedrequest.RequestCacheAwareFilter import org.springframework.security.web.session.ConcurrentSessionFilter import org.springframework.security.web.session.SessionManagementFilter +import javax.servlet.http.HttpServletRequest +import javax.servlet.http.HttpServletResponse + +import static org.junit.Assert.assertSame +import static org.mockito.Matchers.any +import static org.mockito.Mockito.verify + /** * Tests session-related functionality for the <http> namespace element and <session-management> * @@ -164,7 +163,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests { then: concurrentSessionFilter instanceof ConcurrentSessionFilter - concurrentSessionFilter.expiredUrl == '/expired' + concurrentSessionFilter.expiredSessionStrategy.destinationUrl == '/expired' appContext.getBean("sr") != null getFilter(SessionManagementFilter.class) != null sessionRegistryIsValid(); @@ -271,7 +270,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests { List filters = getFilters("/someurl"); expect: - filters.get(1).expiredUrl == null + filters.get(1).expiredSessionStrategy == null } def externalSessionStrategyIsSupported() { diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 8fa1cf387c..35d1ff5f61 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -8592,6 +8592,9 @@ Session-management related functionality is implemented by the addition of a `Se * **invalid-session-url** 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. +[[nsa-session-management-invalid-session-strategy-ref]] +* **invalid-session-url** +Allows injection of the InvalidSessionStrategy instance used by the SessionManagementFilter. Use either this or the `invalid-session-url` attribute but not both. [[nsa-session-management-session-authentication-error-url]] * **session-authentication-error-url** @@ -8646,6 +8649,9 @@ If set to "true" a `SessionAuthenticationException` will be raised when a user a * **expired-url** The URL a user will be redirected to if they attempt to use a session which has been "expired" by the concurrent session controller because the user has exceeded the number of allowed sessions and has logged in again elsewhere. Should be set unless `exception-if-maximum-exceeded` is set. If no value is supplied, an expiry message will just be written directly back to the response. +[[nsa-concurrency-control-expired-session-strategy-ref]] +* **expired-url** +Allows injection of the ExpiredSessionStrategy instance used by the ConcurrentSessionFilter [[nsa-concurrency-control-max-sessions]] * **max-sessions** 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 75d6fd77d4..298676372b 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 @@ -17,7 +17,6 @@ package org.springframework.security.web.session; import java.io.IOException; - import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; @@ -35,7 +34,6 @@ import org.springframework.security.web.RedirectStrategy; import org.springframework.security.web.authentication.logout.CompositeLogoutHandler; import org.springframework.security.web.authentication.logout.LogoutHandler; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; -import org.springframework.security.web.util.UrlUtils; import org.springframework.util.Assert; import org.springframework.web.filter.GenericFilterBean; @@ -51,8 +49,8 @@ import org.springframework.web.filter.GenericFilterBean; * as expired. If it has been marked as expired, the configured logout handlers will be * called (as happens with * {@link org.springframework.security.web.authentication.logout.LogoutFilter}), typically - * to invalidate the session. A redirect to the expiredURL specified will be performed, - * and the session invalidation will cause an + * to invalidate the session. To handle the expired session a call to the {@link ExpiredSessionStrategy} is made. + * The session invalidation will cause an * {@link org.springframework.security.web.session.HttpSessionDestroyedEvent} to be * published via the * {@link org.springframework.security.web.session.HttpSessionEventPublisher} registered @@ -61,15 +59,15 @@ import org.springframework.web.filter.GenericFilterBean; * * @author Ben Alex * @author EddĂș MelĂ©ndez + * @author Marten Deinum */ public class ConcurrentSessionFilter extends GenericFilterBean { // ~ Instance fields // ================================================================================================ - private SessionRegistry sessionRegistry; - private String expiredUrl; - private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); + private final SessionRegistry sessionRegistry; private LogoutHandler handlers = new CompositeLogoutHandler(new SecurityContextLogoutHandler()); + private ExpiredSessionStrategy expiredSessionStrategy; // ~ Methods // ======================================================================================================== @@ -81,17 +79,13 @@ public class ConcurrentSessionFilter extends GenericFilterBean { public ConcurrentSessionFilter(SessionRegistry sessionRegistry, String expiredUrl) { Assert.notNull(sessionRegistry, "SessionRegistry required"); - Assert.isTrue(expiredUrl == null || UrlUtils.isValidRedirectUrl(expiredUrl), - expiredUrl + " isn't a valid redirect URL"); this.sessionRegistry = sessionRegistry; - this.expiredUrl = expiredUrl; + this.expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy(expiredUrl); } @Override public void afterPropertiesSet() { Assert.notNull(sessionRegistry, "SessionRegistry required"); - Assert.isTrue(expiredUrl == null || UrlUtils.isValidRedirectUrl(expiredUrl), - expiredUrl + " isn't a valid redirect URL"); } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) @@ -108,12 +102,14 @@ public class ConcurrentSessionFilter extends GenericFilterBean { if (info != null) { if (info.isExpired()) { // Expired - abort processing + if (logger.isDebugEnabled()) { + logger.debug("Requested session ID " + + request.getRequestedSessionId() + " has expired."); + } doLogout(request, response); - String targetUrl = determineExpiredUrl(request, info); - - if (targetUrl != null) { - redirectStrategy.sendRedirect(request, response, targetUrl); + if (this.expiredSessionStrategy != null) { + this.expiredSessionStrategy.onExpiredSessionDetected(request, response); return; } @@ -136,10 +132,6 @@ public class ConcurrentSessionFilter extends GenericFilterBean { chain.doFilter(request, response); } - protected String determineExpiredUrl(HttpServletRequest request, - SessionInformation info) { - return expiredUrl; - } private void doLogout(HttpServletRequest request, HttpServletResponse response) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); @@ -151,7 +143,7 @@ public class ConcurrentSessionFilter extends GenericFilterBean { this.handlers = new CompositeLogoutHandler(handlers); } - public void setRedirectStrategy(RedirectStrategy redirectStrategy) { - this.redirectStrategy = redirectStrategy; + public void setExpiredSessionStrategy(ExpiredSessionStrategy expiredSessionStrategy) { + this.expiredSessionStrategy=expiredSessionStrategy; } } diff --git a/web/src/main/java/org/springframework/security/web/session/ExpiredSessionStrategy.java b/web/src/main/java/org/springframework/security/web/session/ExpiredSessionStrategy.java new file mode 100644 index 0000000000..0c8908cfbc --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/session/ExpiredSessionStrategy.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015-2016 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 + * + * http://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 java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Determines the behaviour of the {@code ConcurrentSessionFilter} when an expired session + * is detected in the {@code ConcurrentSessionFilter}. + * + * @author Marten Deinum + * @since 4.1.0 + */ +public interface ExpiredSessionStrategy { + + void onExpiredSessionDetected(HttpServletRequest request, HttpServletResponse response) + throws IOException, ServletException; + +} diff --git a/web/src/main/java/org/springframework/security/web/session/SimpleRedirectExpiredSessionStrategy.java b/web/src/main/java/org/springframework/security/web/session/SimpleRedirectExpiredSessionStrategy.java new file mode 100644 index 0000000000..31a68f59cc --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/session/SimpleRedirectExpiredSessionStrategy.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2016 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 + * + * http://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 java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +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; + +/** + * Performs a redirect to a fixed URL when an expired session is detected by the + * {@code ConcurrentSessionFilter}. + * + * @author Marten Deinum + * @since 4.1.0 + */ +public final class SimpleRedirectExpiredSessionStrategy implements ExpiredSessionStrategy { + private final Log logger = LogFactory.getLog(getClass()); + private final String destinationUrl; + private final RedirectStrategy redirectStrategy; + + public SimpleRedirectExpiredSessionStrategy(String invalidSessionUrl) { + this(invalidSessionUrl, new DefaultRedirectStrategy()); + } + + public SimpleRedirectExpiredSessionStrategy(String invalidSessionUrl, RedirectStrategy redirectStrategy) { + Assert.isTrue(UrlUtils.isValidRedirectUrl(invalidSessionUrl), + "url must start with '/' or with 'http(s)'"); + this.destinationUrl=invalidSessionUrl; + this.redirectStrategy=redirectStrategy; + } + + public void onExpiredSessionDetected(HttpServletRequest request, + HttpServletResponse response) throws IOException { + logger.debug("Redirecting to '" + destinationUrl + "'"); + redirectStrategy.sendRedirect(request, response, destinationUrl); + } + +} 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 f02ec7d214..0dd2530397 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 @@ -16,11 +16,7 @@ package org.springframework.security.web.concurrent; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - import java.util.Date; - import javax.servlet.FilterChain; import org.junit.Test; @@ -29,10 +25,13 @@ import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockHttpSession; import org.springframework.security.core.session.SessionRegistry; import org.springframework.security.core.session.SessionRegistryImpl; -import org.springframework.security.web.DefaultRedirectStrategy; 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.SimpleRedirectExpiredSessionStrategy; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; /** * Tests {@link ConcurrentSessionFilter}. @@ -56,9 +55,10 @@ public class ConcurrentSessionFilterTests { registry.getSessionInformation(session.getId()).expireNow(); // Setup our test fixture and registry to want this session to be expired - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, - "/expired.jsp"); - filter.setRedirectStrategy(new DefaultRedirectStrategy()); + + SimpleRedirectExpiredSessionStrategy expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy("/expired.jsp"); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry); + filter.setExpiredSessionStrategy(expiredSessionStrategy); filter.setLogoutHandlers(new LogoutHandler[] { new SecurityContextLogoutHandler() }); filter.afterPropertiesSet(); @@ -97,11 +97,6 @@ public class ConcurrentSessionFilterTests { new ConcurrentSessionFilter(null); } - @Test(expected = IllegalArgumentException.class) - public void detectsInvalidUrl() throws Exception { - new ConcurrentSessionFilter(new SessionRegistryImpl(), "ImNotValid"); - } - @Test public void lastRequestTimeUpdatesCorrectly() throws Exception { // Setup our HTTP request @@ -115,11 +110,11 @@ public class ConcurrentSessionFilterTests { // Setup our test fixture SessionRegistry registry = new SessionRegistryImpl(); registry.registerNewSession(session.getId(), "principal"); - ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, - "/expired.jsp"); + SimpleRedirectExpiredSessionStrategy expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy("/expired.jsp"); + ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry); + filter.setExpiredSessionStrategy(expiredSessionStrategy); - Date lastRequest = registry.getSessionInformation(session.getId()) - .getLastRequest(); + Date lastRequest = registry.getSessionInformation(session.getId()).getLastRequest(); Thread.sleep(1000);