SEC-583: Implementation of namespace config for concurrent session support.

Also some minor adjustments to ordering of different http features in schema.
This commit is contained in:
Luke Taylor 2007-10-27 00:28:24 +00:00
parent 334d55b12e
commit d3b165749f
8 changed files with 167 additions and 50 deletions

View File

@ -0,0 +1,70 @@
package org.springframework.security.config;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
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.concurrent.ConcurrentSessionControllerImpl;
import org.springframework.security.concurrent.ConcurrentSessionFilter;
import org.springframework.security.concurrent.SessionRegistryImpl;
import org.springframework.security.providers.ProviderManager;
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 DEFAULT_SESSION_REGISTRY_ID = "_sessionRegistry";
static final String DEFAULT_CONCURRENT_SESSION_FILTER_ID = "_concurrentSessionFilter";
static final String DEFAULT_SESSION_CONTROLLER_ID = "_concurrentSessionController";
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinitionRegistry beanRegistry = parserContext.getRegistry();
RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class);
BeanDefinitionBuilder filterBuilder =
BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionFilter.class);
BeanDefinitionBuilder controllerBuilder
= BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class);
controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(DEFAULT_SESSION_REGISTRY_ID));
filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(DEFAULT_SESSION_REGISTRY_ID));
String expiryUrl = element.getAttribute("expiryUrl");
if (StringUtils.hasText(expiryUrl)) {
filterBuilder.addPropertyValue("expiryUrl", expiryUrl);
}
String maxSessions = element.getAttribute("maxSessions");
if (StringUtils.hasText(expiryUrl)) {
controllerBuilder.addPropertyValue("maximumSessions", maxSessions);
}
String exceptionIfMaximumExceeded = element.getAttribute("exceptionIfMaximumExceeded");
if (StringUtils.hasText(expiryUrl)) {
controllerBuilder.addPropertyValue("exceptionIfMaximumExceeded", exceptionIfMaximumExceeded);
}
BeanDefinition controller = controllerBuilder.getBeanDefinition();
beanRegistry.registerBeanDefinition(DEFAULT_SESSION_REGISTRY_ID, sessionRegistry);
beanRegistry.registerBeanDefinition(DEFAULT_SESSION_CONTROLLER_ID, controller);
beanRegistry.registerBeanDefinition(DEFAULT_CONCURRENT_SESSION_FILTER_ID, filterBuilder.getBeanDefinition());
BeanDefinition providerManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext);
providerManager.getPropertyValues().addPropertyValue("sessionController", controller);
return null;
}
}

View File

@ -40,9 +40,10 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
public static final String DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID = "_exceptionTranslationFilter";
public static final String DEFAULT_FILTER_SECURITY_INTERCEPTOR_ID = "_filterSecurityInterceptor";
public static final String CONCURRENT_SESSIONS_ELEMENT = "concurrent-session-control";
public static final String LOGOUT_ELEMENT = "logout";
public static final String FORM_LOGIN_ELEMENT = "form-login";
public static final String BASIC_AUTH_ELEMENT = "http-basic";
public static final String BASIC_AUTH_ELEMENT = "http-basic";
static final String PATH_PATTERN_ATTRIBUTE = "pattern";
static final String PATTERN_TYPE_ATTRIBUTE = "pathType";
@ -92,7 +93,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
filterChainMap, interceptorFilterInvDefSource);
// TODO: if empty, set a default set a default /**, omitting login url
Element sessionControlElt = DomUtils.getChildElementByTagName(element, CONCURRENT_SESSIONS_ELEMENT);
if (sessionControlElt != null) {
new ConcurrentSessionsBeanDefinitionParser().parse(sessionControlElt, parserContext);
}
BeanDefinitionRegistry registry = parserContext.getRegistry();
@ -112,7 +118,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
if (basicAuthElt != null) {
new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext);
}
}
registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy);
registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF);

View File

@ -1,16 +1,17 @@
namespace beans = "http://www.springframework.org/schema/beans"
namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
namespace security = "http://www.springframework.org/schema/security"
datatypes xsd = "http://www.w3.org/2001/XMLSchema-datatypes"
#default namespace = "http://www.springframework.org/schema/security"
default namespace = "http://www.springframework.org/schema/security"
# targetNamespace="http://www.springframework.org/schema/security"
path-type =
## Defines the type types of pattern used to specify URL paths. Defaults to "ant"
[ a:defaultValue = "ant" ] attribute pathType {"regex" | "ant"}
autoconfig =
## Provides automatic security configration for a application
element autoconfig {autoconfig.attlist, empty}
@ -47,7 +48,7 @@ protect.attlist &=
http =
## Container element for HTTP security configuration
element http {http.attlist, intercept-url+, logout?, form-login?, http-basic? }
element http {http.attlist, intercept-url+, form-login?, http-basic?, logout?, concurrent-session-control? }
http.attlist &=
## Controls the eagerness with which an HTTP session is created.
[ a:defaultValue = "ifRequired" ] attribute createSession {"ifRequired" | "always" | "never" }?
@ -57,6 +58,11 @@ http.attlist &=
http.attlist &=
## Whether test URLs should be converted to lower case prior to comparing with defined path patterns.
[ a:defaultValue = "true" ] attribute lowerCaseComparisons {"true" | "false"}?
http.attlist &=
## Optional attribute specifying the ID of the AccessDecisionManager implementation which should be
## used for authorizing HTTP requests.
attribute accessDecisionManager {xsd:string}?
intercept-url =
## Specifies the access attributes and/or filter list for a particular set of URLs.
@ -72,6 +78,9 @@ intercept-url.attlist &=
## The full filter stack (consisting of all defined filters, will be applied to any other paths).
intercept-url.attlist &=
attribute filters {"none"}?
intercept-url.attlist &=
## Used to specify that a URL must be accessed over http or https
attribute requiresChannel {"http" | "https"}?
logout =
element logout {logout.attlist, empty}
@ -115,13 +124,16 @@ http-basic =
http-basic.attlist &=
attribute realm {xsd:string}
concurrent-sessions =
concurrent-session-control =
## Adds support for concurrent session control, allowing limits to be placed on the number of sessions a
## user can have.
element concurrent-sessions {concurrent-sessions.attlist, empty}
element concurrent-session-control {concurrent-sessions.attlist, empty}
concurrent-sessions.attlist &=
attribute maxSessions {xsd:positiveInteger}?
concurrent-sessions.attlist &=
attribute expiredUrl {xsd:string}?
concurrent-sessions.attlist &=
attribute exceptionIfMaximumExceeded {"true" | "false"}?
authentication-provider =
element authentication-provider {authentication-provider.attlist, (user-service | jdbc-user-service)}

View File

@ -1,10 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns="http://www.springframework.org/schema/security"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<!-- default namespace = "http://www.springframework.org/schema/security" -->
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" targetNamespace="http://www.springframework.org/schema/security" xmlns:security="http://www.springframework.org/schema/security">
<!-- targetNamespace="http://www.springframework.org/schema/security" -->
<xs:attributeGroup name="path-type">
<xs:attribute name="pathType" use="required">
<xs:annotation>
@ -29,7 +25,7 @@
<xs:documentation>Sets up an ldap authentication provider, optionally with an embedded ldap server</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="ldap.attlist"/>
<xs:attributeGroup ref="security:ldap.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="ldap.attlist">
@ -47,7 +43,7 @@
<xs:element name="intercept-methods">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="protect"/>
<xs:element maxOccurs="unbounded" ref="security:protect"/>
</xs:sequence>
</xs:complexType>
</xs:element>
@ -56,7 +52,7 @@
<xs:documentation>Defines a protected method and the access control configuration attributes that apply to it</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="protect.attlist"/>
<xs:attributeGroup ref="security:protect.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="protect.attlist">
@ -77,12 +73,13 @@
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="intercept-url"/>
<xs:element minOccurs="0" ref="logout"/>
<xs:element minOccurs="0" ref="form-login"/>
<xs:element minOccurs="0" ref="http-basic"/>
<xs:element maxOccurs="unbounded" ref="security:intercept-url"/>
<xs:element minOccurs="0" ref="security:form-login"/>
<xs:element minOccurs="0" ref="security:http-basic"/>
<xs:element minOccurs="0" ref="security:logout"/>
<xs:element minOccurs="0" ref="security:concurrent-session-control"/>
</xs:sequence>
<xs:attributeGroup ref="http.attlist"/>
<xs:attributeGroup ref="security:http.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="http.attlist">
@ -120,13 +117,14 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="accessDecisionManager" type="xs:string"/>
</xs:attributeGroup>
<xs:element name="intercept-url">
<xs:annotation>
<xs:documentation>Specifies the access attributes and/or filter list for a particular set of URLs.</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="intercept-url.attlist"/>
<xs:attributeGroup ref="security:intercept-url.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="intercept-url.attlist">
@ -143,10 +141,21 @@
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="requiresChannel">
<xs:annotation>
<xs:documentation>Used to specify that a URL must be accessed over http or https</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="http"/>
<xs:enumeration value="https"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="logout">
<xs:complexType>
<xs:attributeGroup ref="logout.attlist"/>
<xs:attributeGroup ref="security:logout.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="logout.attlist">
@ -166,7 +175,7 @@
<xs:documentation>Sets up a form login configuration</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="form-login.attlist"/>
<xs:attributeGroup ref="security:form-login.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="form-login.attlist">
@ -187,17 +196,17 @@
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="filter-chain"/>
<xs:element maxOccurs="unbounded" ref="security:filter-chain"/>
</xs:sequence>
<xs:attributeGroup ref="filter-chain-map.attlist"/>
<xs:attributeGroup ref="security:filter-chain-map.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="filter-chain-map.attlist">
<xs:attributeGroup ref="path-type"/>
<xs:attributeGroup ref="security:path-type"/>
</xs:attributeGroup>
<xs:element name="filter-chain">
<xs:complexType>
<xs:attributeGroup ref="filter-chain.attlist"/>
<xs:attributeGroup ref="security:filter-chain.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="filter-chain.attlist">
@ -209,35 +218,44 @@
<xs:documentation>Adds support for basic authentication</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="http-basic.attlist"/>
<xs:attributeGroup ref="security:http-basic.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="http-basic.attlist">
<xs:attribute name="realm" use="required" type="xs:string"/>
</xs:attributeGroup>
<xs:element name="concurrent-sessions">
<xs:element name="concurrent-session-control">
<xs:complexType>
<xs:attributeGroup ref="concurrent-sessions.attlist"/>
<xs:attributeGroup ref="security:concurrent-sessions.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="concurrent-sessions.attlist">
<xs:attribute name="maxSessions" type="xs:positiveInteger"/>
<xs:attribute name="expiredUrl" type="xs:string"/>
<xs:attribute name="exceptionIfMaximumExceeded">
<xs:simpleType>
<xs:restriction base="xs:token">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="authentication-provider">
<xs:complexType>
<xs:choice>
<xs:element ref="user-service"/>
<xs:element ref="jdbc-user-service"/>
<xs:element ref="security:user-service"/>
<xs:element ref="security:jdbc-user-service"/>
</xs:choice>
</xs:complexType>
</xs:element>
<xs:element name="user-service">
<xs:complexType>
<xs:choice>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="user"/>
<xs:element ref="jdbc-user-service"/>
<xs:element minOccurs="0" maxOccurs="unbounded" ref="security:user"/>
<xs:element ref="security:jdbc-user-service"/>
</xs:choice>
<xs:attributeGroup ref="user-service.attlist"/>
<xs:attributeGroup ref="security:user-service.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="user-service.attlist">
@ -245,7 +263,7 @@
</xs:attributeGroup>
<xs:element name="user">
<xs:complexType>
<xs:attributeGroup ref="user.attlist"/>
<xs:attributeGroup ref="security:user.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="user.attlist">

View File

@ -5,6 +5,7 @@ import static org.junit.Assert.assertTrue;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.security.concurrent.ConcurrentSessionFilter;
import org.springframework.security.context.HttpSessionContextIntegrationFilter;
import org.springframework.security.intercept.web.FilterSecurityInterceptor;
import org.springframework.security.ui.ExceptionTranslationFilter;
@ -52,11 +53,12 @@ public class HttpSecurityBeanDefinitionParserTests {
(FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
List filterList = filterChainProxy.getFilters("/someurl");
assertTrue("Expected 7 filterList in chain", filterList.size() == 7);
assertTrue("Expected 8 filters in chain", filterList.size() == 8);
Iterator filters = filterList.iterator();
assertTrue(filters.next() instanceof ConcurrentSessionFilter);
assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter);
assertTrue(filters.next() instanceof LogoutFilter);
assertTrue(filters.next() instanceof AuthenticationProcessingFilter);

View File

@ -11,14 +11,16 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
<security:intercept-url pattern="/somepath" access="ROLE_SPECIAL,ROLE_USER" />
<security:intercept-url pattern="/**" access="ROLE_USER" />
<!-- Default logout configuration -->
<security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />
<!-- Default form login configuration. Will create filter and entry point -->
<security:form-login loginUrl="/j_spring_security_check" />
<!-- Default basic auth configuration. Will create filter and entry point -->
<security:http-basic realm="NamespaceTestRealm" />
<!-- Default logout configuration -->
<security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />
<security:concurrent-session-control maxSessions="1"/>
</security:http>
<security:authentication-provider>

View File

@ -12,16 +12,15 @@
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
<security:autoconfig />
<security:http>
<security:http>
<security:intercept-url pattern="/secure/extreme/**" access="ROLE_SUPERVISOR"/>
<security:intercept-url pattern="/secure/**" access="IS_AUTHENTICATED_REMEMBERED" />
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<security:logout />
<security:form-login />
<security:http-basic realm="SpringSecurityTutorialApp" />
<security:logout />
<security:concurrent-session-control maxSessions="1" exceptionIfMaximumExceeded="true"/>
</security:http>

View File

@ -47,7 +47,15 @@
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<welcome-file-list>
<!--
- Publishes events for session creation and destruction through the application
- context. Optional unless concurrent session control is being used.
-->
<listener>
<listener-class>org.springframework.security.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>