Polish SessionInformationExpiredStrategy

* Fix passivity and add tests
* Introduce SessionInformationExpiredEvent as a value object
* Rename ExpiredSessionStrategy to SessionInformationExpiredStrategy
  to account for the need of SessionInformation
* Switch to Constructor Injection
* Move the changes to the xsd to 4.2 xsd instead of 4.1

Issue gh-3808
This commit is contained in:
Rob Winch 2016-09-15 14:30:52 -05:00
parent 67c9f12964
commit 6650429283
14 changed files with 458 additions and 79 deletions

View File

@ -48,11 +48,11 @@ 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.SessionInformationExpiredStrategy;
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.session.SimpleRedirectSessionInformationExpiredStrategy;
import org.springframework.util.Assert;
/**
@ -99,7 +99,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
private SessionAuthenticationStrategy sessionAuthenticationStrategy;
private SessionAuthenticationStrategy providedSessionAuthenticationStrategy;
private InvalidSessionStrategy invalidSessionStrategy;
private ExpiredSessionStrategy expiredSessionStrategy;
private SessionInformationExpiredStrategy expiredSessionStrategy;
private List<SessionAuthenticationStrategy> sessionAuthenticationStrategies = new ArrayList<SessionAuthenticationStrategy>();
private SessionRegistry sessionRegistry;
private Integer maximumSessions;
@ -355,7 +355,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
}
public ConcurrencyControlConfigurer expiredSessionStrategy(
ExpiredSessionStrategy expiredSessionStrategy) {
SessionInformationExpiredStrategy expiredSessionStrategy) {
SessionManagementConfigurer.this.expiredSessionStrategy = expiredSessionStrategy;
return this;
}
@ -470,16 +470,23 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
http.addFilter(sessionManagementFilter);
if (isConcurrentSessionControlEnabled()) {
ConcurrentSessionFilter concurrentSessionFilter = new ConcurrentSessionFilter(
getSessionRegistry(http));
concurrentSessionFilter
.setExpiredSessionStrategy(getExpiredSessionStrategy());
ConcurrentSessionFilter concurrentSessionFilter = createConccurencyFilter(http);
concurrentSessionFilter = postProcess(concurrentSessionFilter);
http.addFilter(concurrentSessionFilter);
}
}
private ConcurrentSessionFilter createConccurencyFilter(H http) {
SessionInformationExpiredStrategy expireStrategy = getExpiredSessionStrategy();
SessionRegistry sessionRegistry = getSessionRegistry(http);
if(expireStrategy == null) {
return new ConcurrentSessionFilter(sessionRegistry);
}
return new ConcurrentSessionFilter(sessionRegistry, expireStrategy);
}
/**
* Gets the {@link InvalidSessionStrategy} to use. If null and
* {@link #invalidSessionUrl} is not null defaults to
@ -505,7 +512,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
return this.invalidSessionStrategy;
}
ExpiredSessionStrategy getExpiredSessionStrategy() {
SessionInformationExpiredStrategy getExpiredSessionStrategy() {
if (this.expiredSessionStrategy != null) {
return this.expiredSessionStrategy;
}
@ -515,7 +522,7 @@ public final class SessionManagementConfigurer<H extends HttpSecurityBuilder<H>>
}
if (this.expiredSessionStrategy == null) {
this.expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy(
this.expiredSessionStrategy = new SimpleRedirectSessionInformationExpiredStrategy(
this.expiredUrl);
}
return this.expiredSessionStrategy;

View File

@ -65,7 +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.SimpleRedirectSessionInformationExpiredStrategy;
import org.springframework.security.web.session.SimpleRedirectInvalidSessionStrategy;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.ClassUtils;
@ -511,11 +511,11 @@ class HttpConfigurationBuilder {
if (StringUtils.hasText(expiryUrl)) {
BeanDefinitionBuilder expiredSessionBldr = BeanDefinitionBuilder
.rootBeanDefinition(SimpleRedirectExpiredSessionStrategy.class);
.rootBeanDefinition(SimpleRedirectSessionInformationExpiredStrategy.class);
expiredSessionBldr.addConstructorArgValue(expiryUrl);
filterBuilder.addPropertyValue("expiredSessionStrategy", expiredSessionBldr.getBeanDefinition());
filterBuilder.addConstructorArgValue(expiredSessionBldr.getBeanDefinition());
} else if (StringUtils.hasText(expiredSessionStrategyRef)) {
filterBuilder.addPropertyReference("expiredSessionStrategy", expiredSessionStrategyRef);
filterBuilder.addConstructorArgReference(expiredSessionStrategyRef);
}
pc.popAndRegisterContainingComponent();

View File

@ -535,9 +535,6 @@ 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}?
@ -556,9 +553,6 @@ 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}?

View File

@ -1743,13 +1743,6 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="invalid-session-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows injection of the InvalidSessionStrategy instance used by the
SessionManagementFilter
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="session-authentication-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows injection of the SessionAuthenticationStrategy instance used by the
@ -1784,13 +1777,6 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="expired-session-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows injection of the ExpiredSessionStrategy instance used by the
ConcurrentSessionFilter
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="error-if-maximum-exceeded" type="xs:boolean">
<xs:annotation>
<xs:documentation>Specifies that an unauthorized error should be reported when a user attempts to login when

View File

@ -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 SessionInformationExpiredStrategy 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}?

View File

@ -1743,6 +1743,13 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="invalid-session-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows injection of the InvalidSessionStrategy instance used by the
SessionManagementFilter
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="session-authentication-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows injection of the SessionAuthenticationStrategy instance used by the
@ -1777,6 +1784,13 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="expired-session-strategy-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Allows injection of the SessionInformationExpiredStrategy instance used by the
ConcurrentSessionFilter
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="error-if-maximum-exceeded" type="xs:boolean">
<xs:annotation>
<xs:documentation>Specifies that an unauthorized error should be reported when a user attempts to login when

View File

@ -67,7 +67,7 @@ class NamespaceSessionManagementTests extends BaseSpringSpec {
concurrentStrategy.maximumSessions == 1
concurrentStrategy.exceptionIfMaximumExceeded
concurrentStrategy.sessionRegistry == CustomSessionManagementConfig.SR
findFilter(ConcurrentSessionFilter).expiredSessionStrategy.destinationUrl == "/expired-session"
findFilter(ConcurrentSessionFilter).sessionInformationExpiredStrategy.destinationUrl == "/expired-session"
}
@EnableWebSecurity

View File

@ -163,7 +163,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
then:
concurrentSessionFilter instanceof ConcurrentSessionFilter
concurrentSessionFilter.expiredSessionStrategy.destinationUrl == '/expired'
concurrentSessionFilter.sessionInformationExpiredStrategy.destinationUrl == '/expired'
appContext.getBean("sr") != null
getFilter(SessionManagementFilter.class) != null
sessionRegistryIsValid();
@ -270,7 +270,7 @@ class SessionManagementConfigTests extends AbstractHttpConfigTests {
List filters = getFilters("/someurl");
expect:
filters.get(1).expiredSessionStrategy == null
filters.get(1).sessionInformationExpiredStrategy.class.name == 'org.springframework.security.web.session.ConcurrentSessionFilter$ResponseBodySessionInformationExpiredStrategy'
}
def externalSessionStrategyIsSupported() {

View File

@ -17,6 +17,7 @@
package org.springframework.security.web.session;
import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@ -29,11 +30,11 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.DefaultRedirectStrategy;
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;
@ -49,7 +50,7 @@ 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. To handle the expired session a call to the {@link ExpiredSessionStrategy} is made.
* to invalidate the session. To handle the expired session a call to the {@link SessionInformationExpiredStrategy} is made.
* The session invalidation will cause an
* {@link org.springframework.security.web.session.HttpSessionDestroyedEvent} to be
* published via the
@ -62,12 +63,15 @@ import org.springframework.web.filter.GenericFilterBean;
* @author Marten Deinum
*/
public class ConcurrentSessionFilter extends GenericFilterBean {
// ~ Instance fields
// ================================================================================================
private final SessionRegistry sessionRegistry;
private String expiredUrl;
private RedirectStrategy redirectStrategy;
private LogoutHandler handlers = new CompositeLogoutHandler(new SecurityContextLogoutHandler());
private ExpiredSessionStrategy expiredSessionStrategy;
private SessionInformationExpiredStrategy sessionInformationExpiredStrategy;
// ~ Methods
// ========================================================================================================
@ -75,12 +79,42 @@ public class ConcurrentSessionFilter extends GenericFilterBean {
public ConcurrentSessionFilter(SessionRegistry sessionRegistry) {
Assert.notNull(sessionRegistry, "SessionRegistry required");
this.sessionRegistry = sessionRegistry;
this.sessionInformationExpiredStrategy = new ResponseBodySessionInformationExpiredStrategy();
}
/**
* Creates a new instance
*
* @param sessionRegistry the SessionRegistry to use
* @param expiredUrl the URL to redirect to
* @deprecated use {@link #ConcurrentSessionFilter(SessionRegistry, SessionInformationExpiredStrategy)} with {@link SimpleRedirectSessionInformationExpiredStrategy} instead.
*/
@Deprecated
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.expiredUrl = expiredUrl;
this.sessionRegistry = sessionRegistry;
this.expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy(expiredUrl);
this.sessionInformationExpiredStrategy = new SessionInformationExpiredStrategy() {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {
HttpServletRequest request = event.getRequest();
HttpServletResponse response = event.getResponse();
SessionInformation info = event.getSessionInformation();
redirectStrategy.sendRedirect(request, response, determineExpiredUrl(request, info));
}
};
}
public ConcurrentSessionFilter(SessionRegistry sessionRegistry, SessionInformationExpiredStrategy sessionInformationExpiredStrategy) {
Assert.notNull(sessionRegistry, "sessionRegistry required");
Assert.notNull(sessionInformationExpiredStrategy, "sessionInformationExpiredStrategy cannot be null");
this.sessionRegistry = sessionRegistry;
this.sessionInformationExpiredStrategy = sessionInformationExpiredStrategy;
}
@Override
@ -108,18 +142,7 @@ public class ConcurrentSessionFilter extends GenericFilterBean {
}
doLogout(request, response);
if (this.expiredSessionStrategy != null) {
this.expiredSessionStrategy.onExpiredSessionDetected(request, response);
return;
}
else {
response.getWriter().print(
"This session has been expired (possibly due to multiple concurrent "
+ "logins being attempted as the same user).");
response.flushBuffer();
}
this.sessionInformationExpiredStrategy.onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
return;
}
else {
@ -132,6 +155,17 @@ public class ConcurrentSessionFilter extends GenericFilterBean {
chain.doFilter(request, response);
}
/**
* Determine the URL for expiration
* @param request the HttpServletRequest
* @param info the {@link SessionInformation}
* @return the URL for expiration
* @deprecated Use {@link #ConcurrentSessionFilter(SessionRegistry, SessionInformationExpiredStrategy)} instead.
*/
protected String determineExpiredUrl(HttpServletRequest request,
SessionInformation info) {
return expiredUrl;
}
private void doLogout(HttpServletRequest request, HttpServletResponse response) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
@ -143,7 +177,30 @@ public class ConcurrentSessionFilter extends GenericFilterBean {
this.handlers = new CompositeLogoutHandler(handlers);
}
public void setExpiredSessionStrategy(ExpiredSessionStrategy expiredSessionStrategy) {
this.expiredSessionStrategy=expiredSessionStrategy;
/**
* Sets the {@link RedirectStrategy} used with {@link #ConcurrentSessionFilter(SessionRegistry, String)}
* @param redirectStrategy the {@link RedirectStrategy} to use
* @deprecated use {@link #ConcurrentSessionFilter(SessionRegistry, SessionInformationExpiredStrategy)} instead.
*/
public void setRedirectStrategy(RedirectStrategy redirectStrategy) {
this.redirectStrategy = redirectStrategy;
}
/**
* A {@link SessionInformationExpiredStrategy} that writes an error message to the response body.
* @author Rob Winch
* @since 4.2
*/
private static final class ResponseBodySessionInformationExpiredStrategy
implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event)
throws IOException, ServletException {
HttpServletResponse response = event.getResponse();
response.getWriter().print(
"This session has been expired (possibly due to multiple concurrent "
+ "logins being attempted as the same user).");
response.flushBuffer();
}
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2012-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 javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.context.ApplicationEvent;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.util.Assert;
/**
* An event for when a {@link SessionInformation} is expired.
* @author Rob Winch
* @since 4.2
*/
public final class SessionInformationExpiredEvent extends ApplicationEvent {
private HttpServletRequest request;
private HttpServletResponse response;
/**
* Creates a new instance
*
* @param sessionInformation the SessionInformation that is expired
* @param request the HttpServletRequest
* @param response the HttpServletResponse
*/
public SessionInformationExpiredEvent(SessionInformation sessionInformation, HttpServletRequest request, HttpServletResponse response) {
super(sessionInformation);
Assert.notNull(request, "request cannot be null");
Assert.notNull(response, "response cannot be null");
this.request = request;
this.response = response;
}
/**
* @return the request
*/
public HttpServletRequest getRequest() {
return request;
}
/**
* @return the response
*/
public HttpServletResponse getResponse() {
return response;
}
public SessionInformation getSessionInformation() {
return (SessionInformation) getSource();
}
}

View File

@ -16,20 +16,19 @@
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
* @author Rob Winch
* @since 4.2.0
*/
public interface ExpiredSessionStrategy {
public interface SessionInformationExpiredStrategy {
void onExpiredSessionDetected(HttpServletRequest request, HttpServletResponse response)
void onExpiredSessionDetected(SessionInformationExpiredEvent eventØ)
throws IOException, ServletException;
}

View File

@ -16,11 +16,10 @@
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;
@ -31,28 +30,27 @@ import org.springframework.util.Assert;
* {@code ConcurrentSessionFilter}.
*
* @author Marten Deinum
* @since 4.1.0
* @since 4.2.0
*/
public final class SimpleRedirectExpiredSessionStrategy implements ExpiredSessionStrategy {
public final class SimpleRedirectSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
private final Log logger = LogFactory.getLog(getClass());
private final String destinationUrl;
private final RedirectStrategy redirectStrategy;
public SimpleRedirectExpiredSessionStrategy(String invalidSessionUrl) {
public SimpleRedirectSessionInformationExpiredStrategy(String invalidSessionUrl) {
this(invalidSessionUrl, new DefaultRedirectStrategy());
}
public SimpleRedirectExpiredSessionStrategy(String invalidSessionUrl, RedirectStrategy redirectStrategy) {
public SimpleRedirectSessionInformationExpiredStrategy(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 {
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
logger.debug("Redirecting to '" + destinationUrl + "'");
redirectStrategy.sendRedirect(request, response, destinationUrl);
redirectStrategy.sendRedirect(event.getRequest(), event.getResponse(), destinationUrl);
}
}

View File

@ -17,21 +17,37 @@
package org.springframework.security.web.concurrent;
import java.util.Date;
import javax.servlet.FilterChain;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import org.junit.After;
import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.RedirectStrategy;
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 org.springframework.security.web.session.SessionInformationExpiredStrategy;
import org.springframework.security.web.session.SimpleRedirectSessionInformationExpiredStrategy;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
/**
* Tests {@link ConcurrentSessionFilter}.
@ -41,6 +57,33 @@ import static org.mockito.Mockito.*;
*/
public class ConcurrentSessionFilterTests {
@After
public void cleanup() {
SecurityContextHolder.clearContext();
}
@Test(expected = IllegalArgumentException.class)
public void constructorSessionRegistryWhenSessionRegistryNullThenExceptionThrown() {
new ConcurrentSessionFilter(null);
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void constructorSessionRegistryExpiresUrlWhenInvalidUrlThenExceptionThrown() {
new ConcurrentSessionFilter(new SessionRegistryImpl(), "oops");
}
@SuppressWarnings("deprecation")
@Test(expected = IllegalArgumentException.class)
public void constructorSessionRegistryExpiresUrlWhenSessionRegistryNullThenExceptionThrown() {
new ConcurrentSessionFilter(null, "/expired");
}
@Test(expected = IllegalArgumentException.class)
public void constructorSessionRegistrySessionInformationExpiredStrategyWhenStrategyIsNullThenThrowsException() {
new ConcurrentSessionFilter(new SessionRegistryImpl(), (SessionInformationExpiredStrategy) null);
}
@Test
public void detectsExpiredSessions() throws Exception {
// Setup our HTTP request
@ -56,9 +99,8 @@ public class ConcurrentSessionFilterTests {
// Setup our test fixture and registry to want this session to be expired
SimpleRedirectExpiredSessionStrategy expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy("/expired.jsp");
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry);
filter.setExpiredSessionStrategy(expiredSessionStrategy);
SimpleRedirectSessionInformationExpiredStrategy expiredSessionStrategy = new SimpleRedirectSessionInformationExpiredStrategy("/expired.jsp");
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredSessionStrategy);
filter.setLogoutHandlers(new LogoutHandler[] { new SecurityContextLogoutHandler() });
filter.afterPropertiesSet();
@ -110,9 +152,8 @@ public class ConcurrentSessionFilterTests {
// Setup our test fixture
SessionRegistry registry = new SessionRegistryImpl();
registry.registerNewSession(session.getId(), "principal");
SimpleRedirectExpiredSessionStrategy expiredSessionStrategy = new SimpleRedirectExpiredSessionStrategy("/expired.jsp");
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry);
filter.setExpiredSessionStrategy(expiredSessionStrategy);
SimpleRedirectSessionInformationExpiredStrategy expiredSessionStrategy = new SimpleRedirectSessionInformationExpiredStrategy("/expired.jsp");
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry, expiredSessionStrategy);
Date lastRequest = registry.getSessionInformation(session.getId()).getLastRequest();
@ -123,4 +164,165 @@ public class ConcurrentSessionFilterTests {
verify(fc).doFilter(request, response);
assertThat(registry.getSessionInformation(session.getId()).getLastRequest().after(lastRequest)).isTrue();
}
@Test
public void doFilterWhenNoSessionThenChainIsContinued() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
RedirectStrategy redirect = mock(RedirectStrategy.class);
SessionRegistry registry = mock(SessionRegistry.class);
SessionInformation information = new SessionInformation("user", "sessionId", new Date(System.currentTimeMillis() - 1000));
information.expireNow();
when(registry.getSessionInformation(anyString())).thenReturn(information);
String expiredUrl = "/expired";
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry,
expiredUrl);
filter.setRedirectStrategy(redirect);
MockFilterChain chain = new MockFilterChain();
filter.doFilter(request, response, chain);
assertThat(chain.getRequest()).isNotNull();
}
@Test
public void doFilterWhenNoSessionInformationThenChainIsContinued() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setSession(new MockHttpSession());
MockHttpServletResponse response = new MockHttpServletResponse();
RedirectStrategy redirect = mock(RedirectStrategy.class);
SessionRegistry registry = mock(SessionRegistry.class);
String expiredUrl = "/expired";
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry,
expiredUrl);
filter.setRedirectStrategy(redirect);
MockFilterChain chain = new MockFilterChain();
filter.doFilter(request, response, chain);
assertThat(chain.getRequest()).isNotNull();
}
@Test
public void doFilterWhenCustomRedirectStrategyThenCustomRedirectStrategyUsed() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpSession session = new MockHttpSession();
request.setSession(session);
MockHttpServletResponse response = new MockHttpServletResponse();
RedirectStrategy redirect = mock(RedirectStrategy.class);
SessionRegistry registry = mock(SessionRegistry.class);
SessionInformation information = new SessionInformation("user", "sessionId", new Date(System.currentTimeMillis() - 1000));
information.expireNow();
when(registry.getSessionInformation(anyString())).thenReturn(information);
String expiredUrl = "/expired";
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry,
expiredUrl);
filter.setRedirectStrategy(redirect);
filter.doFilter(request, response, new MockFilterChain());
verify(redirect).sendRedirect(request, response, expiredUrl);
}
@Test
public void doFilterWhenOverrideThenCustomRedirectStrategyUsed() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpSession session = new MockHttpSession();
request.setSession(session);
MockHttpServletResponse response = new MockHttpServletResponse();
RedirectStrategy redirect = mock(RedirectStrategy.class);
SessionRegistry registry = mock(SessionRegistry.class);
SessionInformation information = new SessionInformation("user", "sessionId", new Date(System.currentTimeMillis() - 1000));
information.expireNow();
when(registry.getSessionInformation(anyString())).thenReturn(information);
final String expiredUrl = "/expired";
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry,
expiredUrl + "will-be-overrridden") {
/* (non-Javadoc)
* @see org.springframework.security.web.session.ConcurrentSessionFilter#determineExpiredUrl(javax.servlet.http.HttpServletRequest, org.springframework.security.core.session.SessionInformation)
*/
@Override
protected String determineExpiredUrl(HttpServletRequest request,
SessionInformation info) {
return expiredUrl;
}
};
filter.setRedirectStrategy(redirect);
filter.doFilter(request, response, new MockFilterChain());
verify(redirect).sendRedirect(request, response, expiredUrl);
}
@Test
public void doFilterWhenNoExpiredUrlThenResponseWritten() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpSession session = new MockHttpSession();
request.setSession(session);
MockHttpServletResponse response = new MockHttpServletResponse();
SessionRegistry registry = mock(SessionRegistry.class);
SessionInformation information = new SessionInformation("user", "sessionId", new Date(System.currentTimeMillis() - 1000));
information.expireNow();
when(registry.getSessionInformation(anyString())).thenReturn(information);
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry);
filter.doFilter(request, response, new MockFilterChain());
assertThat(response.getContentAsString()).contains(
"This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).");
}
@Test
public void doFilterWhenCustomLogoutHandlersThenHandlersUsed() throws Exception {
LogoutHandler handler = mock(LogoutHandler.class);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpSession session = new MockHttpSession();
request.setSession(session);
MockHttpServletResponse response = new MockHttpServletResponse();
SessionRegistry registry = mock(SessionRegistry.class);
SessionInformation information = new SessionInformation("user", "sessionId", new Date(System.currentTimeMillis() - 1000));
information.expireNow();
when(registry.getSessionInformation(anyString())).thenReturn(information);
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(registry);
filter.setLogoutHandlers(new LogoutHandler[] { handler } );
filter.doFilter(request, response, new MockFilterChain());
verify(handler).logout(eq(request), eq(response), any(Authentication.class));
}
@Test(expected = IllegalArgumentException.class)
public void setLogoutHandlersWhenNullThenThrowsException() {
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(new SessionRegistryImpl());
filter.setLogoutHandlers(null);
}
@Test(expected = IllegalArgumentException.class)
public void setLogoutHandlersWhenEmptyThenThrowsException() {
ConcurrentSessionFilter filter = new ConcurrentSessionFilter(new SessionRegistryImpl());
filter.setLogoutHandlers(new LogoutHandler[0]);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright 2012-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.util.Date;
import org.junit.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.core.session.SessionInformation;
/**
* @author Rob Winch
* @since 4.2
*/
public class SessionInformationExpiredEventTests {
@Test(expected = IllegalArgumentException.class)
public void constructorWhenSessionInformationNullThenThrowsException() {
new SessionInformationExpiredEvent(null, new MockHttpServletRequest(), new MockHttpServletResponse());
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenRequestNullThenThrowsException() {
new SessionInformationExpiredEvent(new SessionInformation("fake", "sessionId", new Date()), null, new MockHttpServletResponse());
}
@Test(expected = IllegalArgumentException.class)
public void constructorWhenResponseNullThenThrowsException() {
new SessionInformationExpiredEvent(new SessionInformation("fake", "sessionId", new Date()), new MockHttpServletRequest(), null);
}
}