From 910e63f83cf3f68efaa7177770226337dc0ca659 Mon Sep 17 00:00:00 2001 From: Luke Taylor Date: Sun, 11 Nov 2007 22:07:46 +0000 Subject: [PATCH] SEC-586: Implemented secure channel support in namespace configuration. --- .../HttpSecurityBeanDefinitionParser.java | 86 +++++++++++++++---- .../HttpSecurityConfigPostProcessor.java | 2 +- .../ChannelProcessingFilter.java | 56 +++++------- ...HttpSecurityBeanDefinitionParserTests.java | 13 +-- .../ChannelProcessingFilterTests.java | 10 --- .../security/config/http-security.xml | 4 +- 6 files changed, 102 insertions(+), 69 deletions(-) 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 bed550efa9..c4fe9acd80 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java @@ -1,27 +1,39 @@ package org.springframework.security.config; -import org.springframework.beans.factory.config.BeanDefinition; -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.ConfigAttributeDefinition; import org.springframework.security.ConfigAttributeEditor; import org.springframework.security.context.HttpSessionContextIntegrationFilter; +import org.springframework.security.intercept.web.AbstractFilterInvocationDefinitionSource; import org.springframework.security.intercept.web.FilterInvocationDefinitionMap; import org.springframework.security.intercept.web.FilterSecurityInterceptor; import org.springframework.security.intercept.web.PathBasedFilterInvocationDefinitionMap; import org.springframework.security.intercept.web.RegExpBasedFilterInvocationDefinitionMap; +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.ui.ExceptionTranslationFilter; import org.springframework.security.util.FilterChainProxy; import org.springframework.security.util.RegexUrlPathMatcher; +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.util.Assert; import org.springframework.util.StringUtils; import org.springframework.util.xml.DomUtils; + import org.w3c.dom.Element; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; /** * Sets up HTTP security: filter stack and protected URLs. @@ -38,6 +50,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { public static final String DEFAULT_LOGOUT_FILTER_ID = "_logoutFilter"; public static final String DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID = "_exceptionTranslationFilter"; public static final String DEFAULT_FILTER_SECURITY_INTERCEPTOR_ID = "_filterSecurityInterceptor"; + public static final String DEFAULT_CHANNEL_PROCESSING_FILTER_ID = "_channelProcessingFilter"; + public static final String DEFAULT_CHANNEL_DECISION_MANAGER_ID = "_channelDecisionManager"; public static final String CONCURRENT_SESSIONS_ELEMENT = "concurrent-session-control"; public static final String LOGOUT_ELEMENT = "logout"; @@ -53,6 +67,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { static final String NO_FILTERS_VALUE = "none"; private static final String ACCESS_CONFIG_ATTRIBUTE = "access"; + private static final String REQUIRES_CHANNEL_ATTRIBUTE = "requiresChannel"; public BeanDefinition parse(Element element, ParserContext parserContext) { RootBeanDefinition filterChainProxy = new RootBeanDefinition(FilterChainProxy.class); @@ -78,10 +93,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { String patternType = element.getAttribute(PATTERN_TYPE_ATTRIBUTE); FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap(); + FilterInvocationDefinitionMap channelFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap(); if (patternType.equals(PATTERN_TYPE_REGEX)) { filterChainProxy.getPropertyValues().addPropertyValue("matcher", new RegexUrlPathMatcher()); interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap(); + channelFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap(); } filterChainProxy.getPropertyValues().addPropertyValue("filterChainMap", filterChainMap); @@ -92,7 +109,28 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { //filterSecurityInterceptorBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE); parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"), - filterChainMap, interceptorFilterInvDefSource); + 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 + RootBeanDefinition channelFilter = new RootBeanDefinition(ChannelProcessingFilter.class); + channelFilter.getPropertyValues().addPropertyValue("channelDecisionManager", + new RuntimeBeanReference(DEFAULT_CHANNEL_DECISION_MANAGER_ID)); + + channelFilter.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource", + channelFilterInvDefSource); + RootBeanDefinition channelDecisionManager = new RootBeanDefinition(ChannelDecisionManagerImpl.class); + List channelProcessors = new ArrayList(2); + channelProcessors.add(new SecureChannelProcessor()); + channelProcessors.add(new InsecureChannelProcessor()); + channelDecisionManager.getPropertyValues().addPropertyValue("channelProcessors", channelProcessors); + + registry.registerBeanDefinition(DEFAULT_CHANNEL_PROCESSING_FILTER_ID, channelFilter); + registry.registerBeanDefinition(DEFAULT_CHANNEL_DECISION_MANAGER_ID, channelDecisionManager); + } Element sessionControlElt = DomUtils.getChildElementByTagName(element, CONCURRENT_SESSIONS_ELEMENT); @@ -100,8 +138,8 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { new ConcurrentSessionsBeanDefinitionParser().parse(sessionControlElt, parserContext); } - // Parse remember me before logout as RememberMeServices is also a LogoutHandler implementation. - BeanDefinitionRegistry registry = parserContext.getRegistry(); + // Parse remember me before logout as RememberMeServices is also a LogoutHandler implementation. + Element rememberMeElt = DomUtils.getChildElementByTagName(element, REMEMBER_ME_ELEMENT); @@ -149,11 +187,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { * FilterInvocationDefinitionSource used in FilterSecurityInterceptor. */ private void parseInterceptUrls(List urlElts, Map filterChainMap, - FilterInvocationDefinitionMap interceptorFilterInvDefSource) { + FilterInvocationDefinitionMap interceptorFilterInvDefSource, + FilterInvocationDefinitionMap channelFilterInvDefSource, ParserContext parserContext) { Iterator urlEltsIterator = urlElts.iterator(); - ConfigAttributeEditor attributeEditor = new ConfigAttributeEditor(); + ConfigAttributeEditor editor = new ConfigAttributeEditor(); while (urlEltsIterator.hasNext()) { Element urlElt = (Element) urlEltsIterator.next(); @@ -166,18 +205,33 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser { // Convert the comma-separated list of access attributes to a ConfigAttributeDefinition if (StringUtils.hasText(access)) { - attributeEditor.setAsText(access); + editor.setAsText(access); + interceptorFilterInvDefSource.addSecureUrl(path, (ConfigAttributeDefinition) editor.getValue()); + } + + String requiredChannel = urlElt.getAttribute(REQUIRES_CHANNEL_ATTRIBUTE); - ConfigAttributeDefinition attributeDef = (ConfigAttributeDefinition) attributeEditor.getValue(); + if (StringUtils.hasText(requiredChannel)) { + String channelConfigAttribute = null; + + if (requiredChannel.equals("https")) { + channelConfigAttribute = "REQUIRES_SECURE_CHANNEL"; + } else if (requiredChannel.equals("http")) { + channelConfigAttribute = "REQUIRES_INSECURE_CHANNEL"; + } else { + parserContext.getReaderContext().error("Unsupported channel " + requiredChannel, urlElt); + } - interceptorFilterInvDefSource.addSecureUrl(path, attributeDef); + editor.setAsText(channelConfigAttribute); + channelFilterInvDefSource.addSecureUrl(path, (ConfigAttributeDefinition) editor.getValue()); } String filters = urlElt.getAttribute(FILTERS_ATTRIBUTE); if (StringUtils.hasText(filters)) { if (!filters.equals(NO_FILTERS_VALUE)) { - throw new IllegalStateException("Currently only 'none' is supported as the custom filters attribute"); + parserContext.getReaderContext().error("Currently only 'none' is supported as the custom " + + "filters attribute", urlElt); } filterChainMap.put(path, Collections.EMPTY_LIST); diff --git a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java index 3e254f26a5..649ff8f037 100644 --- a/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java +++ b/core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java @@ -163,7 +163,7 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor if (!(filter instanceof Ordered)) { // TODO: Possibly log this as a warning and skip this filter. - throw new IllegalArgumentException("Filter " + id + " must implement the Ordered interface"); + throw new SecurityConfigurationException("Filter " + id + " must implement the Ordered interface"); } orderedFilters.add(filter); diff --git a/core/src/main/java/org/springframework/security/securechannel/ChannelProcessingFilter.java b/core/src/main/java/org/springframework/security/securechannel/ChannelProcessingFilter.java index 5f44c7189e..47b489615b 100644 --- a/core/src/main/java/org/springframework/security/securechannel/ChannelProcessingFilter.java +++ b/core/src/main/java/org/springframework/security/securechannel/ChannelProcessingFilter.java @@ -17,47 +17,40 @@ package org.springframework.security.securechannel; import org.springframework.security.ConfigAttribute; import org.springframework.security.ConfigAttributeDefinition; - import org.springframework.security.intercept.web.FilterInvocation; import org.springframework.security.intercept.web.FilterInvocationDefinitionSource; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - +import org.springframework.security.ui.SpringSecurityFilter; +import org.springframework.security.ui.FilterChainOrderUtils; import org.springframework.beans.factory.InitializingBean; - import org.springframework.util.Assert; -import java.io.IOException; - -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; -import javax.servlet.Filter; import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; /** - * Ensures a web request is delivered over the required channel.

Internally uses a {@link FilterInvocation} to - * represent the request, so that the FilterInvocation-related property editors and lookup classes can be - * used.

- *

Delegates the actual channel security decisions and necessary actions to the configured {@link - * ChannelDecisionManager}. If a response is committed by the ChannelDecisionManager, the filter chain - * will not proceed.

- *

Do not use this class directly. Instead configure web.xml to use the {@link + * Ensures a web request is delivered over the required channel. + *

Internally uses a {@link FilterInvocation} to represent the request, so that the + * FilterInvocation-related property editors and lookup classes can be used.

+ *

Delegates the actual channel security decisions and necessary actions to the configured + * {@link ChannelDecisionManager}. If a response is committed by the ChannelDecisionManager, + * the filter chain will not proceed.

+ *

Do not use this class directly. Instead configure web.xml to use the {@link * org.springframework.security.util.FilterToBeanProxy}.

* * @author Ben Alex * @version $Id$ */ -public class ChannelProcessingFilter implements InitializingBean, Filter { +public class ChannelProcessingFilter extends SpringSecurityFilter implements InitializingBean { //~ Static fields/initializers ===================================================================================== private static final Log logger = LogFactory.getLog(ChannelProcessingFilter.class); @@ -108,17 +101,8 @@ public class ChannelProcessingFilter implements InitializingBean, Filter { } } - public void destroy() {} - - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + public void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { - if (!(request instanceof HttpServletRequest)) { - throw new ServletException("HttpServletRequest required"); - } - - if (!(response instanceof HttpServletResponse)) { - throw new ServletException("HttpServletResponse required"); - } FilterInvocation fi = new FilterInvocation(request, response, chain); ConfigAttributeDefinition attr = this.filterInvocationDefinitionSource.getAttributes(fi); @@ -146,8 +130,6 @@ public class ChannelProcessingFilter implements InitializingBean, Filter { return filterInvocationDefinitionSource; } - public void init(FilterConfig filterConfig) throws ServletException {} - public void setChannelDecisionManager(ChannelDecisionManager channelDecisionManager) { this.channelDecisionManager = channelDecisionManager; } @@ -155,4 +137,8 @@ public class ChannelProcessingFilter implements InitializingBean, Filter { public void setFilterInvocationDefinitionSource(FilterInvocationDefinitionSource filterInvocationDefinitionSource) { this.filterInvocationDefinitionSource = filterInvocationDefinitionSource; } + + public int getOrder() { + return FilterChainOrderUtils.CHANNEL_PROCESSING_FILTER_ORDER; + } } 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 fddfaa923c..3ec85dc1b0 100644 --- a/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java +++ b/core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java @@ -1,19 +1,21 @@ package org.springframework.security.config; -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.securechannel.ChannelProcessingFilter; import org.springframework.security.ui.ExceptionTranslationFilter; -import org.springframework.security.ui.rememberme.RememberMeProcessingFilter; import org.springframework.security.ui.basicauth.BasicProcessingFilter; import org.springframework.security.ui.logout.LogoutFilter; +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.context.support.ClassPathXmlApplicationContext; import org.junit.AfterClass; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import org.junit.BeforeClass; import org.junit.Test; @@ -56,17 +58,18 @@ public class HttpSecurityBeanDefinitionParserTests { List filterList = filterChainProxy.getFilters("/someurl"); - assertEquals("Expected 9 filters in chain", 9, filterList.size()); + assertEquals("Expected 10 filters in chain", 10, filterList.size()); Iterator filters = filterList.iterator(); + assertTrue(filters.next() instanceof ChannelProcessingFilter); assertTrue(filters.next() instanceof ConcurrentSessionFilter); assertTrue(filters.next() instanceof HttpSessionContextIntegrationFilter); assertTrue(filters.next() instanceof LogoutFilter); assertTrue(filters.next() instanceof AuthenticationProcessingFilter); assertTrue(filters.next() instanceof DefaultLoginPageGeneratingFilter); assertTrue(filters.next() instanceof BasicProcessingFilter); - assertTrue(filters.next() instanceof RememberMeProcessingFilter); + assertTrue(filters.next() instanceof RememberMeProcessingFilter); assertTrue(filters.next() instanceof ExceptionTranslationFilter); assertTrue(filters.next() instanceof FilterSecurityInterceptor); } diff --git a/core/src/test/java/org/springframework/security/securechannel/ChannelProcessingFilterTests.java b/core/src/test/java/org/springframework/security/securechannel/ChannelProcessingFilterTests.java index bbf7236355..102af9fcfe 100644 --- a/core/src/test/java/org/springframework/security/securechannel/ChannelProcessingFilterTests.java +++ b/core/src/test/java/org/springframework/security/securechannel/ChannelProcessingFilterTests.java @@ -48,14 +48,6 @@ import javax.servlet.ServletResponse; public class ChannelProcessingFilterTests extends TestCase { //~ Methods ======================================================================================================== - public static void main(String[] args) { - junit.textui.TestRunner.run(ChannelProcessingFilterTests.class); - } - - public final void setUp() throws Exception { - super.setUp(); - } - public void testDetectsMissingChannelDecisionManager() throws Exception { ChannelProcessingFilter filter = new ChannelProcessingFilter(); @@ -200,7 +192,6 @@ public class ChannelProcessingFilterTests extends TestCase { filter.doFilter(null, new MockHttpServletResponse(), new MockFilterChain()); fail("Should have thrown ServletException"); } catch (ServletException expected) { - assertEquals("HttpServletRequest required", expected.getMessage()); } } @@ -212,7 +203,6 @@ public class ChannelProcessingFilterTests extends TestCase { filter.doFilter(new MockHttpServletRequest(null, null), null, new MockFilterChain()); fail("Should have thrown ServletException"); } catch (ServletException expected) { - assertEquals("HttpServletResponse required", expected.getMessage()); } } 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 60c5933b80..0f407beb1e 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 @@ -8,7 +8,7 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc - + @@ -18,7 +18,7 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc - +