SEC-1229: Added support for parsing error URL in session-management
This commit is contained in:
parent
203cc5a8dc
commit
ebada9fd12
|
@ -1,72 +0,0 @@
|
||||||
package org.springframework.security.config.http;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.config.BeanDefinition;
|
|
||||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
|
||||||
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
|
||||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
|
||||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
|
||||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
|
||||||
import org.springframework.beans.factory.xml.ParserContext;
|
|
||||||
import org.springframework.security.authentication.ProviderManager;
|
|
||||||
import org.springframework.security.authentication.concurrent.ConcurrentSessionControllerImpl;
|
|
||||||
import org.springframework.security.authentication.concurrent.SessionRegistryImpl;
|
|
||||||
import org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up support for concurrent session support control, creating {@link ConcurrentSessionFilter},
|
|
||||||
* {@link SessionRegistryImpl} and {@link ConcurrentSessionControllerImpl}. The session controller is also registered
|
|
||||||
* with the default {@link ProviderManager} (which is automatically registered during namespace configuration).
|
|
||||||
*
|
|
||||||
* @author Luke Taylor
|
|
||||||
* @version $Id$
|
|
||||||
*/
|
|
||||||
public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionParser {
|
|
||||||
|
|
||||||
static final String ATT_EXPIRY_URL = "expired-url";
|
|
||||||
static final String ATT_SESSION_REGISTRY_ALIAS = "session-registry-alias";
|
|
||||||
static final String ATT_SESSION_REGISTRY_REF = "session-registry-ref";
|
|
||||||
|
|
||||||
public BeanDefinition parse(Element element, ParserContext pc) {
|
|
||||||
CompositeComponentDefinition compositeDef =
|
|
||||||
new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));
|
|
||||||
pc.pushContainingComponent(compositeDef);
|
|
||||||
|
|
||||||
BeanDefinitionRegistry beanRegistry = pc.getRegistry();
|
|
||||||
|
|
||||||
String sessionRegistryId = element.getAttribute(ATT_SESSION_REGISTRY_REF);
|
|
||||||
|
|
||||||
if (!StringUtils.hasText(sessionRegistryId)) {
|
|
||||||
// Register an internal SessionRegistryImpl if no external reference supplied.
|
|
||||||
RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class);
|
|
||||||
sessionRegistryId = pc.getReaderContext().registerWithGeneratedName(sessionRegistry);
|
|
||||||
pc.registerComponent(new BeanComponentDefinition(sessionRegistry, sessionRegistryId));
|
|
||||||
}
|
|
||||||
|
|
||||||
String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS);
|
|
||||||
if (StringUtils.hasText(registryAlias)) {
|
|
||||||
beanRegistry.registerAlias(sessionRegistryId, registryAlias);
|
|
||||||
}
|
|
||||||
|
|
||||||
BeanDefinitionBuilder filterBuilder =
|
|
||||||
BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionFilter.class);
|
|
||||||
filterBuilder.addPropertyReference("sessionRegistry", sessionRegistryId);
|
|
||||||
|
|
||||||
Object source = pc.extractSource(element);
|
|
||||||
filterBuilder.getRawBeanDefinition().setSource(source);
|
|
||||||
filterBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
|
|
||||||
|
|
||||||
String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);
|
|
||||||
|
|
||||||
if (StringUtils.hasText(expiryUrl)) {
|
|
||||||
WebConfigUtils.validateHttpRedirect(expiryUrl, pc, source);
|
|
||||||
filterBuilder.addPropertyValue("expiredUrl", expiryUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
pc.popAndRegisterContainingComponent();
|
|
||||||
|
|
||||||
return filterBuilder.getBeanDefinition();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -37,6 +37,7 @@ import org.springframework.security.web.access.expression.WebExpressionVoter;
|
||||||
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
import org.springframework.security.web.access.intercept.DefaultFilterInvocationSecurityMetadataSource;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.web.access.intercept.RequestKey;
|
import org.springframework.security.web.access.intercept.RequestKey;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
import org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter;
|
import org.springframework.security.web.authentication.concurrent.ConcurrentSessionFilter;
|
||||||
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||||
|
@ -192,7 +193,7 @@ class HttpConfigurationBuilder {
|
||||||
|
|
||||||
void createSessionManagementFilters() {
|
void createSessionManagementFilters() {
|
||||||
Element sessionMgmtElt = DomUtils.getChildElementByTagName(httpElt, Elements.SESSION_MANAGEMENT);
|
Element sessionMgmtElt = DomUtils.getChildElementByTagName(httpElt, Elements.SESSION_MANAGEMENT);
|
||||||
Element sessionCtrlEt = null;
|
Element sessionCtrlElt = null;
|
||||||
|
|
||||||
String sessionFixationAttribute = null;
|
String sessionFixationAttribute = null;
|
||||||
String invalidSessionUrl = null;
|
String invalidSessionUrl = null;
|
||||||
|
@ -200,10 +201,10 @@ class HttpConfigurationBuilder {
|
||||||
if (sessionMgmtElt != null) {
|
if (sessionMgmtElt != null) {
|
||||||
sessionFixationAttribute = sessionMgmtElt.getAttribute(ATT_SESSION_FIXATION_PROTECTION);
|
sessionFixationAttribute = sessionMgmtElt.getAttribute(ATT_SESSION_FIXATION_PROTECTION);
|
||||||
invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL);
|
invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL);
|
||||||
sessionCtrlEt = DomUtils.getChildElementByTagName(sessionMgmtElt, Elements.CONCURRENT_SESSIONS);
|
sessionCtrlElt = DomUtils.getChildElementByTagName(sessionMgmtElt, Elements.CONCURRENT_SESSIONS);
|
||||||
|
|
||||||
if (sessionCtrlEt != null) {
|
if (sessionCtrlElt != null) {
|
||||||
createConcurrencyControlFilterAndSessionRegistry(sessionCtrlEt);
|
createConcurrencyControlFilterAndSessionRegistry(sessionCtrlElt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,22 +215,25 @@ class HttpConfigurationBuilder {
|
||||||
boolean sessionFixationProtectionRequired = !sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION);
|
boolean sessionFixationProtectionRequired = !sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION);
|
||||||
|
|
||||||
BeanDefinitionBuilder sessionStrategy;
|
BeanDefinitionBuilder sessionStrategy;
|
||||||
|
String concurrencyErrorUrl = null;
|
||||||
|
|
||||||
if (sessionCtrlEt != null) {
|
if (sessionCtrlElt != null) {
|
||||||
assert sessionRegistryRef != null;
|
assert sessionRegistryRef != null;
|
||||||
sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlAuthenticatedSessionStrategy.class);
|
sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlAuthenticatedSessionStrategy.class);
|
||||||
sessionStrategy.addConstructorArgValue(sessionRegistryRef);
|
sessionStrategy.addConstructorArgValue(sessionRegistryRef);
|
||||||
|
|
||||||
String maxSessions = sessionCtrlEt.getAttribute("max-sessions");
|
String maxSessions = sessionCtrlElt.getAttribute("max-sessions");
|
||||||
|
|
||||||
if (StringUtils.hasText(maxSessions)) {
|
if (StringUtils.hasText(maxSessions)) {
|
||||||
sessionStrategy.addPropertyValue("maximumSessions", maxSessions);
|
sessionStrategy.addPropertyValue("maximumSessions", maxSessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
String exceptionIfMaximumExceeded = sessionCtrlEt.getAttribute("exception-if-maximum-exceeded");
|
String exceptionIfMaximumExceeded = sessionCtrlElt.getAttribute("error-if-maximum-exceeded");
|
||||||
|
|
||||||
if (StringUtils.hasText(exceptionIfMaximumExceeded)) {
|
if (StringUtils.hasText(exceptionIfMaximumExceeded)) {
|
||||||
sessionStrategy.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded);
|
sessionStrategy.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded);
|
||||||
|
|
||||||
|
concurrencyErrorUrl = sessionCtrlElt.getAttribute("error-url");
|
||||||
}
|
}
|
||||||
} else if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl)) {
|
} else if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl)) {
|
||||||
sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(DefaultSessionAuthenticationStrategy.class);
|
sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(DefaultSessionAuthenticationStrategy.class);
|
||||||
|
@ -239,6 +243,11 @@ class HttpConfigurationBuilder {
|
||||||
}
|
}
|
||||||
|
|
||||||
BeanDefinitionBuilder sessionMgmtFilter = BeanDefinitionBuilder.rootBeanDefinition(SessionManagementFilter.class);
|
BeanDefinitionBuilder sessionMgmtFilter = BeanDefinitionBuilder.rootBeanDefinition(SessionManagementFilter.class);
|
||||||
|
RootBeanDefinition failureHandler = new RootBeanDefinition(SimpleUrlAuthenticationFailureHandler.class);
|
||||||
|
if (StringUtils.hasText(concurrencyErrorUrl)) {
|
||||||
|
failureHandler.getPropertyValues().addPropertyValue("defaultFailureUrl", concurrencyErrorUrl);
|
||||||
|
}
|
||||||
|
sessionMgmtFilter.addPropertyValue("authenticationFailureHandler", failureHandler);
|
||||||
sessionMgmtFilter.addConstructorArgValue(contextRepoRef);
|
sessionMgmtFilter.addConstructorArgValue(contextRepoRef);
|
||||||
BeanDefinition strategyBean = sessionStrategy.getBeanDefinition();
|
BeanDefinition strategyBean = sessionStrategy.getBeanDefinition();
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,6 @@ import org.springframework.security.access.ConfigAttribute;
|
||||||
import org.springframework.security.access.SecurityConfig;
|
import org.springframework.security.access.SecurityConfig;
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
import org.springframework.security.authentication.concurrent.ConcurrentLoginException;
|
|
||||||
import org.springframework.security.authentication.concurrent.SessionRegistryImpl;
|
import org.springframework.security.authentication.concurrent.SessionRegistryImpl;
|
||||||
import org.springframework.security.config.BeanIds;
|
import org.springframework.security.config.BeanIds;
|
||||||
import org.springframework.security.config.PostProcessedMockUserDetailsService;
|
import org.springframework.security.config.PostProcessedMockUserDetailsService;
|
||||||
|
@ -71,7 +70,6 @@ import org.springframework.security.web.context.HttpSessionSecurityContextReposi
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||||
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
import org.springframework.security.web.savedrequest.HttpSessionRequestCache;
|
||||||
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
import org.springframework.security.web.savedrequest.RequestCacheAwareFilter;
|
||||||
import org.springframework.security.web.session.SessionAuthenticationStrategy;
|
|
||||||
import org.springframework.security.web.session.SessionManagementFilter;
|
import org.springframework.security.web.session.SessionManagementFilter;
|
||||||
import org.springframework.security.web.wrapper.SecurityContextHolderAwareRequestFilter;
|
import org.springframework.security.web.wrapper.SecurityContextHolderAwareRequestFilter;
|
||||||
import org.springframework.util.ReflectionUtils;
|
import org.springframework.util.ReflectionUtils;
|
||||||
|
@ -704,33 +702,27 @@ public class HttpSecurityBeanDefinitionParserTests {
|
||||||
assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
|
assertSame(sessionRegistry, sessionRegistryFromFormLoginFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected=ConcurrentLoginException.class)
|
@Test
|
||||||
public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception {
|
public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception {
|
||||||
setContext(
|
setContext(
|
||||||
"<http auto-config='true'>" +
|
"<http auto-config='true'>" +
|
||||||
" <session-management>" +
|
" <session-management>" +
|
||||||
" <concurrency-control max-sessions='2' exception-if-maximum-exceeded='true' />" +
|
" <concurrency-control max-sessions='2' error-if-maximum-exceeded='true' error-url='/max-exceeded' />" +
|
||||||
" </session-management>" +
|
" </session-management>" +
|
||||||
"</http>" + AUTH_PROVIDER_XML);
|
"</http>" + AUTH_PROVIDER_XML);
|
||||||
SessionAuthenticationStrategy seshStrategy = (SessionAuthenticationStrategy) FieldUtils.getFieldValue(
|
SessionManagementFilter seshStrategy = (SessionManagementFilter) getFilter(SessionManagementFilter.class);
|
||||||
getFilter(SessionManagementFilter.class), "sessionStrategy");
|
|
||||||
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass");
|
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass");
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(auth);
|
||||||
// Register 2 sessions and then check a third
|
// Register 2 sessions and then check a third
|
||||||
// req.setSession(new MockHttpSession());
|
// req.setSession(new MockHttpSession());
|
||||||
// auth.setDetails(new WebAuthenticationDetails(req));
|
// auth.setDetails(new WebAuthenticationDetails(req));
|
||||||
try {
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
seshStrategy.onAuthentication(auth, new MockHttpServletRequest(), new MockHttpServletResponse());
|
seshStrategy.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||||
} catch (ConcurrentLoginException e) {
|
assertNull(response.getRedirectedUrl());
|
||||||
fail("First login should be allowed");
|
seshStrategy.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||||
}
|
assertNull(response.getRedirectedUrl());
|
||||||
|
seshStrategy.doFilter(new MockHttpServletRequest(), response, new MockFilterChain());
|
||||||
try {
|
assertEquals("/max-exceeded", response.getRedirectedUrl());
|
||||||
seshStrategy.onAuthentication(auth, new MockHttpServletRequest(), new MockHttpServletResponse());
|
|
||||||
} catch (ConcurrentLoginException e) {
|
|
||||||
fail("Second login should be allowed");
|
|
||||||
}
|
|
||||||
|
|
||||||
seshStrategy.onAuthentication(auth, new MockHttpServletRequest(), new MockHttpServletResponse());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue