SEC-738: Add session-registry-alias attribute to concurrent-session-control

http://jira.springframework.org/browse/SEC-738. Added this attribute. Also various bugfixes in handling of attribute names for concurrent session control.
This commit is contained in:
Luke Taylor 2008-03-31 12:01:37 +00:00
parent 07f820f1a6
commit 512c64fb98
6 changed files with 153 additions and 40 deletions

View File

@ -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());

View File

@ -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();

View File

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

View File

@ -2,6 +2,7 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:security="http://www.springframework.org/schema/security" elementFormDefault="qualified"
targetNamespace="http://www.springframework.org/schema/security">
<xs:attributeGroup name="hash">
<xs:attribute name="hash" use="required">
<xs:annotation>
@ -92,35 +93,7 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="password-encoder">
<xs:annotation>
<xs:documentation>element which defines a password encoding strategy. Used by an
authentication provider to convert submitted passwords to hashed versions, for
example.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="salt-source">
<xs:complexType>
<xs:attribute name="user-property" type="xs:string">
<xs:annotation>
<xs:documentation>A property of the UserDetails object which will be
used as salt by a password encoder. Typically something like
"username" might be used. </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="system-wide" type="xs:string">
<xs:annotation>
<xs:documentation>A single value that will be used as the salt for a
password encoder. </xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attributeGroup ref="security:password-encoder.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="password-encoder.attlist">
<xs:attribute name="ref" type="xs:string">
<xs:annotation>
@ -365,7 +338,35 @@
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" ref="security:password-encoder"/>
<xs:element minOccurs="0" name="password-encoder">
<xs:annotation>
<xs:documentation>element which defines a password encoding strategy. Used
by an authentication provider to convert submitted passwords to hashed
versions, for example.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="salt-source">
<xs:complexType>
<xs:attribute name="user-property" type="xs:string">
<xs:annotation>
<xs:documentation>A property of the UserDetails object
which will be used as salt by a password encoder.
Typically something like "username" might be used. </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="system-wide" type="xs:string">
<xs:annotation>
<xs:documentation>A single value that will be used as
the salt for a password encoder. </xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attributeGroup ref="security:password-encoder.attlist"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attributeGroup ref="security:password-compare.attlist"/>
</xs:complexType>
@ -706,6 +707,11 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="entry-point-ref" type="xs:string">
<xs:annotation>
<xs:documentation>Allows a customized AuthenticationEntryPoint to be used.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="intercept-url.attlist">
@ -935,6 +941,12 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="session-registry-alias" type="xs:string">
<xs:annotation>
<xs:documentation>Allows you to define an alias for the SessionRegistry bean in
order to access it in your own configuration</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="remember-me.attlist">
@ -1019,7 +1031,35 @@
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:group ref="security:any-user-service"/>
<xs:element ref="security:password-encoder"/>
<xs:element name="password-encoder">
<xs:annotation>
<xs:documentation>element which defines a password encoding strategy. Used
by an authentication provider to convert submitted passwords to hashed
versions, for example.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="salt-source">
<xs:complexType>
<xs:attribute name="user-property" type="xs:string">
<xs:annotation>
<xs:documentation>A property of the UserDetails object
which will be used as salt by a password encoder.
Typically something like "username" might be used. </xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="system-wide" type="xs:string">
<xs:annotation>
<xs:documentation>A single value that will be used as
the salt for a password encoder. </xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attributeGroup ref="security:password-encoder.attlist"/>
</xs:complexType>
</xs:element>
</xs:choice>
<xs:attributeGroup ref="security:ap.attlist"/>
</xs:complexType>

View File

@ -10,7 +10,7 @@
<xsl:output method="xml" indent="yes"/>
<xsl:variable name="elts-to-inline">
<xsl:text>,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,</xsl:text>
<xsl:text>,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,</xsl:text>
</xsl:variable>
<xsl:template match="xs:element">

View File

@ -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(
"<http auto-config='true'>" +
" <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
"</http>" + 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(
"<http auto-config='true'>" +
" <concurrent-session-control max-sessions='2' exception-if-maximum-exceeded='true' />" +
"</http>" + 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);