diff --git a/core/src/main/java/org/springframework/security/config/BeanIds.java b/core/src/main/java/org/springframework/security/config/BeanIds.java index 83983b4a4a..a18f5b00ad 100644 --- a/core/src/main/java/org/springframework/security/config/BeanIds.java +++ b/core/src/main/java/org/springframework/security/config/BeanIds.java @@ -43,4 +43,5 @@ public abstract class BeanIds { public static final String METHOD_DEFINITION_ATTRIBUTES = "_methodDefinitionAttributes"; public static final String EMBEDDED_APACHE_DS = "_apacheDirectoryServerContainer"; public static final String CONTEXT_SOURCE = "_securityContextSource"; + public static final String PORT_MAPPER = "_portMapper"; } diff --git a/core/src/main/java/org/springframework/security/config/Elements.java b/core/src/main/java/org/springframework/security/config/Elements.java index cad1483fbe..dbd92341e4 100644 --- a/core/src/main/java/org/springframework/security/config/Elements.java +++ b/core/src/main/java/org/springframework/security/config/Elements.java @@ -27,4 +27,6 @@ abstract class Elements { public static final String ANNOTATION_DRIVEN = "annotation-driven"; public static final String PASSWORD_ENCODER = "password-encoder"; public static final String SALT_SOURCE = "salt-source"; + public static final String PORT_MAPPINGS = "port-mappings"; + public static final String PORT_MAPPING = "port-mapping"; } 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 f0851ca964..d949ae0319 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -1,6 +1,5 @@ package org.springframework.security.config; -import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; @@ -12,6 +11,7 @@ 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.support.ManagedList; import org.springframework.beans.factory.xml.BeanDefinitionParser; import org.springframework.beans.factory.xml.ParserContext; import org.springframework.security.ConfigAttributeDefinition; @@ -27,6 +27,8 @@ import org.springframework.security.securechannel.ChannelDecisionManagerImpl; import org.springframework.security.securechannel.ChannelProcessingFilter; import org.springframework.security.securechannel.InsecureChannelProcessor; import org.springframework.security.securechannel.SecureChannelProcessor; +import org.springframework.security.securechannel.RetryWithHttpEntryPoint; +import org.springframework.security.securechannel.RetryWithHttpsEntryPoint; import org.springframework.security.ui.ExceptionTranslationFilter; import org.springframework.security.util.FilterChainProxy; import org.springframework.security.util.RegexUrlPathMatcher; @@ -76,9 +78,16 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String ATT_ACCESS_MGR = "access-decision-manager"; public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinitionRegistry registry = parserContext.getRegistry(); RootBeanDefinition filterChainProxy = new RootBeanDefinition(FilterChainProxy.class); RootBeanDefinition httpScif = new RootBeanDefinition(HttpSessionContextIntegrationFilter.class); + BeanDefinition portMapper = new PortMappingsBeanDefinitionParser().parse( + DomUtils.getChildElementByTagName(element, Elements.PORT_MAPPINGS), parserContext); + registry.registerBeanDefinition(BeanIds.PORT_MAPPER, portMapper); + + RuntimeBeanReference portMapperRef = new RuntimeBeanReference(BeanIds.PORT_MAPPER); + String createSession = element.getAttribute(ATT_CREATE_SESSION); if (OPT_CREATE_SESSION_ALWAYS.equals(createSession)) { httpScif.getPropertyValues().addPropertyValue("allowSessionCreation", Boolean.TRUE); @@ -157,8 +166,6 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"), filterChainMap, interceptorFilterInvDefSource, channelFilterInvDefSource, parserContext); - BeanDefinitionRegistry registry = parserContext.getRegistry(); - // Check if we need to register the channel processing beans if (((AbstractFilterInvocationDefinitionSource)channelFilterInvDefSource).getMapSize() > 0) { // At least one channel requirement has been specified @@ -169,9 +176,17 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { channelFilter.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource", channelFilterInvDefSource); RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class); - List channelProcessors = new ArrayList(2); - channelProcessors.add(new SecureChannelProcessor()); - channelProcessors.add(new InsecureChannelProcessor()); + ManagedList channelProcessors = new ManagedList(2); + RootBeanDefinition secureChannelProcessor = new RootBeanDefinition(SecureChannelProcessor.class); + RootBeanDefinition retryWithHttp = new RootBeanDefinition(RetryWithHttpEntryPoint.class); + RootBeanDefinition retryWithHttps = new RootBeanDefinition(RetryWithHttpsEntryPoint.class); + retryWithHttp.getPropertyValues().addPropertyValue("portMapper", portMapperRef); + retryWithHttps.getPropertyValues().addPropertyValue("portMapper", portMapperRef); + secureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint", retryWithHttps); + RootBeanDefinition inSecureChannelProcessor = new RootBeanDefinition(InsecureChannelProcessor.class); + inSecureChannelProcessor.getPropertyValues().addPropertyValue("entryPoint", retryWithHttp); + channelProcessors.add(secureChannelProcessor); + channelProcessors.add(inSecureChannelProcessor); channelDecisionManager.getPropertyValues().addPropertyValue("channelProcessors", channelProcessors); registry.registerBeanDefinition(BeanIds.CHANNEL_PROCESSING_FILTER, channelFilter); diff --git a/core/src/main/java/org/springframework/security/config/PortMappingsBeanDefinitionParser.java b/core/src/main/java/org/springframework/security/config/PortMappingsBeanDefinitionParser.java new file mode 100644 index 0000000000..9578ec7e1f --- /dev/null +++ b/core/src/main/java/org/springframework/security/config/PortMappingsBeanDefinitionParser.java @@ -0,0 +1,53 @@ +package org.springframework.security.config; + +import org.springframework.security.util.PortMapperImpl; +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.support.RootBeanDefinition; +import org.springframework.util.Assert; +import org.springframework.util.xml.DomUtils; + +import org.w3c.dom.Element; + +import java.util.List; +import java.util.Iterator; +import java.util.Map; +import java.util.HashMap; + +/** + * Parses a port-mappings element, producing a single {@link org.springframework.security.util.PortMapperImpl} + * bean. + * + * @author Luke Taylor + * @version $Id$ + */ +public class PortMappingsBeanDefinitionParser implements BeanDefinitionParser { + public static final String ATT_HTTP_PORT = "http"; + public static final String ATT_HTTPS_PORT = "https"; + + public BeanDefinition parse(Element element, ParserContext parserContext) { + BeanDefinition portMapper = new RootBeanDefinition(PortMapperImpl.class); + + if (element != null) { + List mappingElts = DomUtils.getChildElementsByTagName(element, Elements.PORT_MAPPING); + Assert.notEmpty(mappingElts, "No port-mapping child elements!"); + Map mappings = new HashMap(); + + Iterator iterator = mappingElts.iterator(); + while (iterator.hasNext()) { + Element elt = (Element) iterator.next(); + String httpPort = elt.getAttribute(ATT_HTTP_PORT); + String httpsPort = elt.getAttribute(ATT_HTTPS_PORT); + Assert.notNull(httpPort, "No http port supplied in mapping"); + Assert.notNull(httpsPort, "No https port supplied in mapping"); + + mappings.put(httpPort, httpsPort); + } + + portMapper.getPropertyValues().addPropertyValue("portMappings", mappings); + } + + return portMapper; + } +} diff --git a/core/src/main/java/org/springframework/security/util/PortMapperImpl.java b/core/src/main/java/org/springframework/security/util/PortMapperImpl.java index 4a70b1f387..b992d00400 100644 --- a/core/src/main/java/org/springframework/security/util/PortMapperImpl.java +++ b/core/src/main/java/org/springframework/security/util/PortMapperImpl.java @@ -23,9 +23,10 @@ import java.util.Map; /** - * Concrete implementation of {@link PortMapper} that obtains HTTP:HTTPS pairs from the application context.

By - * default the implementation will assume 80:443 and 8080:8443 are HTTP:HTTPS pairs respectively. If different pairs - * are required, use {@link #setPortMappings(Map)}.

+ * Concrete implementation of {@link PortMapper} that obtains HTTP:HTTPS pairs from the application context. + *

+ * By default the implementation will assume 80:443 and 8080:8443 are HTTP:HTTPS pairs respectively. If different pairs + * are required, use {@link #setPortMappings(Map)}. * * @author Ben Alex * @author colin sampaleanu @@ -75,10 +76,15 @@ public class PortMapperImpl implements PortMapper { } /** - *

Set to override the default HTTP port to HTTPS port mappings of 80:443, and 8080:8443.

- * In a Spring XML ApplicationContext, a definition would look something like this:
-     *   <property name="portMappings">    <map>      <entry key="80"><value>443</value></entry>
-     *       <entry key="8080"><value>8443</value></entry>    </map>  </property>
+ * Set to override the default HTTP port to HTTPS port mappings of 80:443, and 8080:8443. + * In a Spring XML ApplicationContext, a definition would look something like this: + *
+     *  <property name="portMappings">
+     *      <map>
+     *          <entry key="80"><value>443</value></entry>
+     *          <entry key="8080"><value>8443</value></entry>
+     *      </map>
+     * </property>
* * @param newMappings A Map consisting of String keys and String values, where for each entry the key is the string * representation of an integer HTTP port number, and the value is the string representation of the 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 3d9f3fc015..46d6db99a6 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 @@ -97,7 +97,7 @@ annotation-driven.attlist &= http = ## Container element for HTTP security configuration - element http {http.attlist, (intercept-url+ & form-login? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous?) } + element http {http.attlist, (intercept-url+ & form-login? & http-basic? & logout? & concurrent-session-control? & remember-me? & anonymous? & port-mappings) } http.attlist &= ## Automatically registers a login form, BASIC authentication, anonymous authentication, logout services, remember-me and servlet-api-integration. If set to "true", all of these capabilities are added (although you can still customize the configuration of each by providing the respective element). If unspecified, defaults to "false". attribute auto-config {"true" | "false" }? @@ -239,6 +239,18 @@ user.attlist &= ## One of more authorities granted to the user. Separate authorities with a comma (but no space). For example, "ROLE_USER,ROLE_ADMINISTRATOR" attribute authorities {xsd:string} +port-mappings = + ## Defines the list of mappings between http and https ports for use in redirects + element port-mappings {port-mappings.attlist, port-mapping+} + +port-mappings.attlist &= empty + +port-mapping = + element port-mapping {http-port, https-port} + +http-port = attribute http {xsd:integer} + +https-port = attribute https {xsd:integer} jdbc-user-service = ## Causes creation of a JDBC-based UserDetailsService. 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 672f1a7ed4..538659da76 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 @@ -258,6 +258,7 @@ + @@ -585,6 +586,28 @@ + + + Defines the list of mappings between http and https ports for use in redirects + + + + + + + + + + + + + + + + + + + Causes creation of a JDBC-based UserDetailsService. 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 1442581c0c..24f5f05490 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -4,6 +4,10 @@ import org.springframework.security.concurrent.ConcurrentSessionFilter; import org.springframework.security.context.HttpSessionContextIntegrationFilter; import org.springframework.security.intercept.web.FilterSecurityInterceptor; import org.springframework.security.securechannel.ChannelProcessingFilter; +import org.springframework.security.securechannel.ChannelDecisionManager; +import org.springframework.security.securechannel.ChannelDecisionManagerImpl; +import org.springframework.security.securechannel.SecureChannelProcessor; +import org.springframework.security.securechannel.RetryWithHttpsEntryPoint; import org.springframework.security.ui.ExceptionTranslationFilter; import org.springframework.security.ui.basicauth.BasicProcessingFilter; import org.springframework.security.ui.logout.LogoutFilter; @@ -11,9 +15,11 @@ import org.springframework.security.ui.rememberme.RememberMeProcessingFilter; import org.springframework.security.ui.webapp.AuthenticationProcessingFilter; import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter; import org.springframework.security.util.FilterChainProxy; +import org.springframework.security.util.PortMapperImpl; import org.springframework.security.wrapper.SecurityContextHolderAwareRequestFilter; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.beans.BeansException; +import org.springframework.util.ReflectionUtils; import org.junit.AfterClass; import static org.junit.Assert.assertEquals; @@ -79,5 +85,14 @@ public class HttpSecurityBeanDefinitionParserTests { assertTrue(filters.next() instanceof RememberMeProcessingFilter); assertTrue(filters.next() instanceof ExceptionTranslationFilter); assertTrue(filters.next() instanceof FilterSecurityInterceptor); + + } + + @Test + public void portMappingsAreParsedCorrectly() throws Exception { + PortMapperImpl pm = (PortMapperImpl) appContext.getBean(BeanIds.PORT_MAPPER); + assertEquals(1, pm.getTranslatedPortMappings().size()); + assertEquals(Integer.valueOf(9080), pm.lookupHttpPort(9443)); + assertEquals(Integer.valueOf(9443), pm.lookupHttpsPort(9080)); } } diff --git a/core/src/test/resources/org/springframework/security/config/http-security.xml b/core/src/test/resources/org/springframework/security/config/http-security.xml index c9d7001492..86bfd99916 100644 --- a/core/src/test/resources/org/springframework/security/config/http-security.xml +++ b/core/src/test/resources/org/springframework/security/config/http-security.xml @@ -23,6 +23,10 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc + + + +