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:
parent
07f820f1a6
commit
512c64fb98
|
@ -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());
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue