diff --git a/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java index d9903f0401..1b8e14104e 100644 --- a/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/ConcurrentSessionsBeanDefinitionParser.java @@ -23,6 +23,12 @@ import org.w3c.dom.Element; * @version $Id$ */ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionParser { + + static final String ATT_EXPIRY_URL = "expired-url"; + static final String ATT_MAX_SESSIONS = "max-sessions"; + static final String ATT_EXCEPTION_IF_MAX_EXCEEDED = "exception-if-maximum-exceeded"; + static final String ATT_SESSION_REGISTRY_ALIAS = "session-registry-alias"; + public BeanDefinition parse(Element element, ParserContext parserContext) { BeanDefinitionRegistry beanRegistry = parserContext.getRegistry(); @@ -38,26 +44,32 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar filterBuilder.setSource(source); controllerBuilder.setSource(source); - String expiryUrl = element.getAttribute("expiryUrl"); + String expiryUrl = element.getAttribute(ATT_EXPIRY_URL); if (StringUtils.hasText(expiryUrl)) { - filterBuilder.addPropertyValue("expiryUrl", expiryUrl); + filterBuilder.addPropertyValue("expiredUrl", expiryUrl); } - String maxSessions = element.getAttribute("maxSessions"); + String maxSessions = element.getAttribute(ATT_MAX_SESSIONS); - if (StringUtils.hasText(expiryUrl)) { + if (StringUtils.hasText(maxSessions)) { controllerBuilder.addPropertyValue("maximumSessions", maxSessions); } - String exceptionIfMaximumExceeded = element.getAttribute("exceptionIfMaximumExceeded"); + String exceptionIfMaximumExceeded = element.getAttribute(ATT_EXCEPTION_IF_MAX_EXCEEDED); - if (StringUtils.hasText(expiryUrl)) { + if (StringUtils.hasText(exceptionIfMaximumExceeded)) { controllerBuilder.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded); } BeanDefinition controller = controllerBuilder.getBeanDefinition(); beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry); + + String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS); + if (StringUtils.hasText(registryAlias)) { + beanRegistry.registerAlias(BeanIds.SESSION_REGISTRY, registryAlias); + } + beanRegistry.registerBeanDefinition(BeanIds.CONCURRENT_SESSION_CONTROLLER, controller); beanRegistry.registerBeanDefinition(BeanIds.CONCURRENT_SESSION_FILTER, filterBuilder.getBeanDefinition()); diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java index f886c21cf5..1c0c14f640 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -90,7 +90,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String DEF_SERVLET_API_PROVISION = "true"; static final String ATT_ACCESS_MGR = "access-decision-manager-ref"; - static final String ATT_USER_SERVICE_REF = "user-service-ref"; + static final String ATT_USER_SERVICE_REF = "user-service-ref"; public BeanDefinition parse(Element element, ParserContext parserContext) { BeanDefinitionRegistry registry = parserContext.getRegistry(); diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc index d6471b045a..c0a7545a57 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.rnc @@ -211,6 +211,9 @@ http.attlist &= http.attlist &= ## Indicates whether an existing session should be invalidated when a user authenticates and a new session started. If set to "none" no change will be made. "newSession" will create a new empty session. "migrateSession" will create a new session and copy the session attributes to the new session. Defaults to "migrateSession". attribute session-fixation-protection {"none" | "newSession" | "migrateSession" }? +http.attlist &= + ## Allows a customized AuthenticationEntryPoint to be used. + attribute entry-point-ref {xsd:string}? intercept-url = @@ -307,6 +310,10 @@ concurrent-sessions.attlist &= attribute expired-url {xsd:string}? concurrent-sessions.attlist &= attribute exception-if-maximum-exceeded {"true" | "false"}? +concurrent-sessions.attlist &= + ## Allows you to define an alias for the SessionRegistry bean in order to access it in your own configuration + attribute session-registry-alias {xsd:string}? + remember-me = element remember-me {remember-me.attlist} diff --git a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd index 8a67d2919f..ffb9f6fd90 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd +++ b/core/src/main/resources/org/springframework/security/config/spring-security-2.0.xsd @@ -2,6 +2,7 @@ + @@ -92,35 +93,7 @@ - - - element which defines a password encoding strategy. Used by an - authentication provider to convert submitted passwords to hashed versions, for - example. - - - - - - - - A property of the UserDetails object which will be - used as salt by a password encoder. Typically something like - "username" might be used. - - - - - A single value that will be used as the salt for a - password encoder. - - - - - - - - + @@ -365,7 +338,35 @@ - + + + element which defines a password encoding strategy. Used + by an authentication provider to convert submitted passwords to hashed + versions, for example. + + + + + + + + A property of the UserDetails object + which will be used as salt by a password encoder. + Typically something like "username" might be used. + + + + + A single value that will be used as + the salt for a password encoder. + + + + + + + + @@ -706,6 +707,11 @@ + + + Allows a customized AuthenticationEntryPoint to be used. + + @@ -935,6 +941,12 @@ + + + Allows you to define an alias for the SessionRegistry bean in + order to access it in your own configuration + + @@ -1019,7 +1031,35 @@ - + + + element which defines a password encoding strategy. Used + by an authentication provider to convert submitted passwords to hashed + versions, for example. + + + + + + + + A property of the UserDetails object + which will be used as salt by a password encoder. + Typically something like "username" might be used. + + + + + A single value that will be used as + the salt for a password encoder. + + + + + + + + diff --git a/core/src/main/resources/org/springframework/security/config/spring-security.xsl b/core/src/main/resources/org/springframework/security/config/spring-security.xsl index d6f0b7c7be..ecf8ad69b3 100644 --- a/core/src/main/resources/org/springframework/security/config/spring-security.xsl +++ b/core/src/main/resources/org/springframework/security/config/spring-security.xsl @@ -10,7 +10,7 @@ - ,intercept-url,form-login,x509,http-basic,logout,concurrent-session-control,remember-me,anonymous,port-mappings,password-compare-element,salt-source,filter-chain,protect-pointcut, + ,intercept-url,form-login,x509,password-encoder,http-basic,logout,concurrent-session-control,remember-me,anonymous,port-mappings,password-compare-element,salt-source,filter-chain,protect-pointcut, diff --git a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java index d17cc635bb..d3c6c93b9a 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -1,5 +1,10 @@ package org.springframework.security.config; +import org.springframework.security.concurrent.ConcurrentLoginException; +import org.springframework.security.concurrent.ConcurrentSessionController; +import org.springframework.security.concurrent.ConcurrentSessionControllerImpl; +import org.springframework.security.concurrent.ConcurrentSessionFilter; +import org.springframework.security.concurrent.SessionRegistryImpl; import org.springframework.security.context.HttpSessionContextIntegrationFilter; import org.springframework.security.intercept.web.FilterSecurityInterceptor; import org.springframework.security.intercept.web.FilterInvocationDefinitionSource; @@ -7,6 +12,7 @@ import org.springframework.security.intercept.web.FilterInvocation; import org.springframework.security.securechannel.ChannelProcessingFilter; import org.springframework.security.ui.ExceptionTranslationFilter; import org.springframework.security.ui.SessionFixationProtectionFilter; +import org.springframework.security.ui.WebAuthenticationDetails; import org.springframework.security.ui.preauth.x509.X509PreAuthenticatedProcessingFilter; import org.springframework.security.ui.basicauth.BasicProcessingFilter; import org.springframework.security.ui.logout.LogoutFilter; @@ -18,13 +24,17 @@ import org.springframework.security.util.FilterChainProxy; import org.springframework.security.util.PortMapperImpl; import org.springframework.security.util.InMemoryXmlApplicationContext; import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.security.providers.anonymous.AnonymousProcessingFilter; +import org.springframework.security.Authentication; import org.springframework.security.MockFilterChain; import org.springframework.security.ConfigAttributeDefinition; import org.springframework.security.SecurityConfig; +import org.springframework.beans.BeanUtils; import org.springframework.context.support.AbstractXmlApplicationContext; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockHttpSession; import static org.junit.Assert.*; import org.junit.Test; @@ -247,6 +257,50 @@ public class HttpSecurityBeanDefinitionParserTests { assertTrue(filters.get(3) instanceof X509PreAuthenticatedProcessingFilter); } + @Test + public void concurrentSessionSupportAddsFilterAndExpectedBeans() throws Exception { + setContext( + "" + + " " + + "" + AUTH_PROVIDER_XML); + List filters = getFilterChainProxy().getFilters("/someurl"); + + assertTrue(filters.get(0) instanceof ConcurrentSessionFilter); + assertNotNull(appContext.getBean("seshRegistry")); + assertNotNull(appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER)); + } + + @Test(expected=ConcurrentLoginException.class) + public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception { + setContext( + "" + + " " + + "" + AUTH_PROVIDER_XML); + ConcurrentSessionControllerImpl seshController = (ConcurrentSessionControllerImpl) appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER); + UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken("bob", "pass"); + // Register 2 sessions and then check a third + MockHttpServletRequest req = new MockHttpServletRequest(); + req.setSession(new MockHttpSession()); + auth.setDetails(new WebAuthenticationDetails(req)); + try { + seshController.checkAuthenticationAllowed(auth); + } catch (ConcurrentLoginException e) { + fail("First login should be allowed"); + } + seshController.registerSuccessfulAuthentication(auth); + req.setSession(new MockHttpSession()); + try { + seshController.checkAuthenticationAllowed(auth); + } catch (ConcurrentLoginException e) { + fail("Second login should be allowed"); + } + auth.setDetails(new WebAuthenticationDetails(req)); + seshController.registerSuccessfulAuthentication(auth); + req.setSession(new MockHttpSession()); + auth.setDetails(new WebAuthenticationDetails(req)); + seshController.checkAuthenticationAllowed(auth); + } + @Test public void disablingSessionProtectionRemovesFilter() throws Exception { setContext( @@ -254,7 +308,7 @@ public class HttpSecurityBeanDefinitionParserTests { List filters = getFilterChainProxy().getFilters("/someurl"); assertFalse(filters.get(1) instanceof SessionFixationProtectionFilter); - } + } private void setContext(String context) { appContext = new InMemoryXmlApplicationContext(context);