SEC-821: Added support for eternal session registry and concurrent session controller to the 2.0.2 namespace.

This commit is contained in:
Luke Taylor 2008-05-27 13:14:21 +00:00
parent 8b5bbe3800
commit d63536cc0d
6 changed files with 164 additions and 49 deletions

View File

@ -3,28 +3,41 @@ package org.springframework.security.config;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Just registers an alias name for the default ProviderManager used by the namespace
* Registers an alias name for the default ProviderManager used by the namespace
* configuration, allowing users to reference it in their beans and clearly see where the name is
* coming from.
* coming from. Also allows the ConcurrentSessionController to be set on the ProviderManager.
*
* @author Luke Taylor
* @version $Id$
*/
public class AuthenticationManagerBeanDefinitionParser implements BeanDefinitionParser {
private static final String ATT_ALIAS = "alias";
private static final String ATT_SESSION_CONTROLLER_REF = "session-controller-ref";
private static final String ATT_ALIAS = "alias";
public BeanDefinition parse(Element element, ParserContext parserContext) {
BeanDefinition authManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext);
String alias = element.getAttribute(ATT_ALIAS);
if (!StringUtils.hasText(alias)) {
parserContext.getReaderContext().error(ATT_ALIAS + " is required.", element );
}
String sessionControllerRef = element.getAttribute(ATT_SESSION_CONTROLLER_REF);
if (StringUtils.hasText(sessionControllerRef)) {
ConfigUtils.setSessionControllerOnAuthenticationManager(parserContext,
BeanIds.CONCURRENT_SESSION_CONTROLLER, element);
authManager.getPropertyValues().addPropertyValue("sessionController",
new RuntimeBeanReference(sessionControllerRef));
}
parserContext.getRegistry().registerAlias(BeanIds.AUTHENTICATION_MANAGER, alias);
return null;

View File

@ -30,6 +30,7 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
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";
static final String ATT_SESSION_REGISTRY_REF = "session-registry-ref";
public BeanDefinition parse(Element element, ParserContext parserContext) {
CompositeComponentDefinition compositeDef =
@ -38,19 +39,30 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
BeanDefinitionRegistry beanRegistry = parserContext.getRegistry();
RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class);
String sessionRegistryId = element.getAttribute(ATT_SESSION_REGISTRY_REF);
if (!StringUtils.hasText(sessionRegistryId)) {
RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class);
beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry);
parserContext.registerComponent(new BeanComponentDefinition(sessionRegistry, BeanIds.SESSION_REGISTRY));
sessionRegistryId = BeanIds.SESSION_REGISTRY;
} else {
// Register the default ID as an alias so that things like session fixation filter can access it
beanRegistry.registerAlias(sessionRegistryId, BeanIds.SESSION_REGISTRY);
}
String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS);
if (StringUtils.hasText(registryAlias)) {
beanRegistry.registerAlias(sessionRegistryId, registryAlias);
}
BeanDefinitionBuilder filterBuilder =
BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionFilter.class);
BeanDefinitionBuilder controllerBuilder
= BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class);
controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(BeanIds.SESSION_REGISTRY));
filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(BeanIds.SESSION_REGISTRY));
filterBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(sessionRegistryId));
Object source = parserContext.extractSource(element);
filterBuilder.setSource(source);
filterBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
controllerBuilder.setSource(source);
controllerBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);
@ -59,6 +71,12 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
filterBuilder.addPropertyValue("expiredUrl", expiryUrl);
}
BeanDefinitionBuilder controllerBuilder
= BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControllerImpl.class);
controllerBuilder.setSource(source);
controllerBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
controllerBuilder.addPropertyValue("sessionRegistry", new RuntimeBeanReference(sessionRegistryId));
String maxSessions = element.getAttribute(ATT_MAX_SESSIONS);
if (StringUtils.hasText(maxSessions)) {
@ -72,13 +90,6 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
}
BeanDefinition controller = controllerBuilder.getBeanDefinition();
beanRegistry.registerBeanDefinition(BeanIds.SESSION_REGISTRY, sessionRegistry);
parserContext.registerComponent(new BeanComponentDefinition(sessionRegistry, BeanIds.SESSION_REGISTRY));
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);
parserContext.registerComponent(new BeanComponentDefinition(controller, BeanIds.CONCURRENT_SESSION_CONTROLLER));
@ -86,9 +97,7 @@ public class ConcurrentSessionsBeanDefinitionParser implements BeanDefinitionPar
parserContext.registerComponent(new BeanComponentDefinition(filterBuilder.getBeanDefinition(), BeanIds.CONCURRENT_SESSION_FILTER));
ConfigUtils.addHttpFilter(parserContext, new RuntimeBeanReference(BeanIds.CONCURRENT_SESSION_FILTER));
BeanDefinition providerManager = ConfigUtils.registerProviderManagerIfNecessary(parserContext);
providerManager.getPropertyValues().addPropertyValue("sessionController", controller);
ConfigUtils.setSessionControllerOnAuthenticationManager(parserContext, BeanIds.CONCURRENT_SESSION_CONTROLLER, element);
parserContext.popAndRegisterContainingComponent();

View File

@ -7,6 +7,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.RuntimeBeanReference;
@ -22,6 +23,7 @@ import org.springframework.security.vote.AffirmativeBased;
import org.springframework.security.vote.AuthenticatedVoter;
import org.springframework.security.vote.RoleVoter;
import org.springframework.util.StringUtils;
import org.w3c.dom.Element;
/**
* Utility methods used internally by the Spring Security namespace configuration code.
@ -168,4 +170,17 @@ public abstract class ConfigUtils {
}
pc.getReaderContext().warning(url + " is not a valid redirect URL (must start with '/' or http(s))", source);
}
static void setSessionControllerOnAuthenticationManager(ParserContext pc, String beanName, Element sourceElt) {
BeanDefinition authManager = registerProviderManagerIfNecessary(pc);
PropertyValue pv = authManager.getPropertyValues().getPropertyValue("sessionController");
if (pv != null && pv.getValue() != null) {
pc.getReaderContext().error("A session controller has already been set on the authentication manager. " +
"The <concurrent-session-control> element isn't compatible with a custom session controller",
pc.extractSource(sourceElt));
}
authManager.getPropertyValues().addPropertyValue("sessionController", new RuntimeBeanReference(beanName));
}
}

View File

@ -335,13 +335,17 @@ concurrent-session-control =
concurrent-sessions.attlist &=
attribute max-sessions {xsd:positiveInteger}?
concurrent-sessions.attlist &=
## The URL a user will be redirected to if they attempt to use a session which has been "expired" by the concurrent session controller.
attribute expired-url {xsd:string}?
concurrent-sessions.attlist &=
## Specifies that an exception should be raised when a user attempts to login twice. The default behaviour is to expire the original session.
attribute exception-if-maximum-exceeded {boolean}?
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}?
concurrent-sessions.attlist &=
## A reference to an external SessionRegistry implementation which will be used in place of the standard one.
attribute session-registry-ref {xsd:string}?
remember-me =
## Sets up remember-me authentication. If used with the "key" attribute (or no attributes) the cookie-only implementation will be used. Specifying "token-repository-ref" or "remember-me-data-source-ref" will use the more secure, persisten token approach.
@ -408,11 +412,15 @@ x509.attlist &=
user-service-ref?
authentication-manager =
## If you are using namespace configuration with Spring Security, an AuthenticationManager will automatically be registered. This element simple allows you to define an alias to allow you to reference the authentication-manager in your own beans.
## If you are using namespace configuration with Spring Security, an AuthenticationManager will automatically be registered. This element allows you to define an alias to allow you to reference the authentication-manager in your own beans.
element authentication-manager {authman.attlist}
## The alias you wish to use for the AuthenticationManager bean
authman.attlist &=
## The alias you wish to use for the AuthenticationManager bean
attribute alias {xsd:ID}
authman.attlist &=
## Allows the session controller to be set on the internal AuthenticationManager. This should not be used with the <concurrent-session-control /> element
attribute session-controller-ref {xsd:string}?
authentication-provider =
## Indicates that the contained user-service should be used as an authentication source.

View File

@ -489,7 +489,16 @@
</xs:annotation>
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded" ref="security:protect"/>
<xs:element maxOccurs="unbounded" name="protect">
<xs:annotation>
<xs:documentation>Defines a protected method and the access control configuration
attributes that apply to it. We strongly advise you NOT to mix "protect" declarations
with any services provided "global-method-security".</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:protect.attlist"/>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attributeGroup ref="security:intercept-methods.attlist"/>
</xs:complexType>
@ -502,16 +511,6 @@
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="protect">
<xs:annotation>
<xs:documentation>Defines a protected method and the access control configuration attributes
that apply to it. We strongly advise you NOT to mix "protect" declarations with any services
provided "global-method-security".</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attributeGroup ref="security:protect.attlist"/>
</xs:complexType>
</xs:element>
<xs:attributeGroup name="protect.attlist">
<xs:attribute name="method" use="required" type="xs:string">
<xs:annotation>
@ -1014,14 +1013,30 @@
</xs:attributeGroup>
<xs:attributeGroup name="concurrent-sessions.attlist">
<xs:attribute name="max-sessions" type="xs:positiveInteger"/>
<xs:attribute name="expired-url" type="xs:string"/>
<xs:attribute name="exception-if-maximum-exceeded" type="security:boolean"/>
<xs:attribute name="expired-url" type="xs:string">
<xs:annotation>
<xs:documentation>The URL a user will be redirected to if they attempt to use a session
which has been "expired" by the concurrent session controller.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="exception-if-maximum-exceeded" type="security:boolean">
<xs:annotation>
<xs:documentation>Specifies that an exception should be raised when a user attempts to login
twice. The default behaviour is to expire the original session.</xs:documentation>
</xs:annotation>
</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:attribute name="session-registry-ref" type="xs:string">
<xs:annotation>
<xs:documentation>A reference to an external SessionRegistry implementation which will be
used in place of the standard one. </xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:attributeGroup name="remember-me.attlist">
<xs:attribute name="key" type="xs:string">
@ -1130,8 +1145,8 @@
<xs:element name="authentication-manager">
<xs:annotation>
<xs:documentation>If you are using namespace configuration with Spring Security, an
AuthenticationManager will automatically be registered. This element simple allows you to
define an alias to allow you to reference the authentication-manager in your own beans.
AuthenticationManager will automatically be registered. This element allows you to define an
alias to allow you to reference the authentication-manager in your own beans.
</xs:documentation>
</xs:annotation>
<xs:complexType>
@ -1139,11 +1154,19 @@
</xs:complexType>
</xs:element>
<xs:attributeGroup name="authman.attlist">
<xs:annotation>
<xs:documentation>The alias you wish to use for the AuthenticationManager
bean</xs:documentation>
</xs:annotation>
<xs:attribute name="alias" use="required" type="xs:ID"/>
<xs:attribute name="alias" use="required" type="xs:ID">
<xs:annotation>
<xs:documentation>The alias you wish to use for the AuthenticationManager
bean</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="session-controller-ref" type="xs:string">
<xs:annotation>
<xs:documentation>Allows the session controller to be set on the internal
AuthenticationManager. This should not be used with the &lt;concurrent-session-control
/&gt; element</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="authentication-provider">
<xs:annotation>

View File

@ -8,7 +8,6 @@ import java.util.List;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@ -40,7 +39,6 @@ import org.springframework.security.ui.rememberme.NullRememberMeServices;
import org.springframework.security.ui.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.ui.rememberme.RememberMeProcessingFilter;
import org.springframework.security.ui.rememberme.RememberMeServices;
import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
import org.springframework.security.util.FieldUtils;
@ -405,7 +403,7 @@ public class HttpSecurityBeanDefinitionParserTests {
setContext(
"<http auto-config='true'>" +
" <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
"</http>" + AUTH_PROVIDER_XML);
"</http>" + AUTH_PROVIDER_XML);
List filters = getFilters("/someurl");
assertTrue(filters.get(0) instanceof ConcurrentSessionFilter);
@ -413,6 +411,55 @@ public class HttpSecurityBeanDefinitionParserTests {
assertNotNull(appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER));
}
@Test
public void externalSessionRegistryBeanIsConfiguredCorrectly() throws Exception {
setContext(
"<http auto-config='true'>" +
" <concurrent-session-control session-registry-ref='seshRegistry' />" +
"</http>" +
"<b:bean id='seshRegistry' class='org.springframework.security.concurrent.SessionRegistryImpl'/>" +
AUTH_PROVIDER_XML);
Object sessionRegistry = appContext.getBean("seshRegistry");
Object sessionRegistryFromFilter = FieldUtils.getFieldValue(
appContext.getBean(BeanIds.CONCURRENT_SESSION_FILTER),"sessionRegistry");
Object sessionRegistryFromController = FieldUtils.getFieldValue(
appContext.getBean(BeanIds.CONCURRENT_SESSION_CONTROLLER),"sessionRegistry");
Object sessionRegistryFromFixationFilter = FieldUtils.getFieldValue(
appContext.getBean(BeanIds.SESSION_FIXATION_PROTECTION_FILTER),"sessionRegistry");
assertSame(sessionRegistry, sessionRegistryFromFilter);
assertSame(sessionRegistry, sessionRegistryFromController);
assertSame(sessionRegistry, sessionRegistryFromFixationFilter);
}
@Test(expected=BeanDefinitionParsingException.class)
public void concurrentSessionSupportCantBeUsedWithIndependentControllerBean() throws Exception {
setContext(
"<authentication-manager alias='authManager' session-controller-ref='sc'/>" +
"<http auto-config='true'>" +
" <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
"</http>" +
"<b:bean id='sc' class='org.springframework.security.concurrent.ConcurrentSessionControllerImpl'>" +
" <b:property name='sessionRegistry'>" +
" <b:bean class='org.springframework.security.concurrent.SessionRegistryImpl'/>" +
" </b:property>" +
"</b:bean>" + AUTH_PROVIDER_XML);
}
@Test(expected=BeanDefinitionParsingException.class)
public void concurrentSessionSupportCantBeUsedWithIndependentControllerBean2() throws Exception {
setContext(
"<http auto-config='true'>" +
" <concurrent-session-control session-registry-alias='seshRegistry' expired-url='/expired'/>" +
"</http>" +
"<b:bean id='sc' class='org.springframework.security.concurrent.ConcurrentSessionControllerImpl'>" +
" <b:property name='sessionRegistry'>" +
" <b:bean class='org.springframework.security.concurrent.SessionRegistryImpl'/>" +
" </b:property>" +
"</b:bean>" +
"<authentication-manager alias='authManager' session-controller-ref='sc'/>" + AUTH_PROVIDER_XML);
}
@Test(expected=ConcurrentLoginException.class)
public void concurrentSessionMaxSessionsIsCorrectlyConfigured() throws Exception {
setContext(