Bladeren bron

SEC-586: Implemented secure channel support in namespace configuration.

Luke Taylor 18 jaren geleden
bovenliggende
commit
910e63f83c

+ 70 - 16
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);

+ 1 - 1
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);

+ 21 - 35
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.<p>Internally uses a {@link FilterInvocation} to
- * represent the request, so that the <code>FilterInvocation</code>-related property editors and lookup classes can be
- * used.</p>
- *  <P>Delegates the actual channel security decisions and necessary actions to the configured {@link
- * ChannelDecisionManager}. If a response is committed by the <code>ChannelDecisionManager</code>, the filter chain
- * will not proceed.</p>
- *  <P><B>Do not use this class directly.</B> Instead configure <code>web.xml</code> to use the {@link
+ * Ensures a web request is delivered over the required channel.
+ * <p>Internally uses a {@link FilterInvocation} to represent the request, so that the
+ * <code>FilterInvocation</code>-related property editors and lookup classes can be used.</p>
+ * <p>Delegates the actual channel security decisions and necessary actions to the configured
+ * {@link ChannelDecisionManager}. If a response is committed by the <code>ChannelDecisionManager</code>,
+ * the filter chain will not proceed.</p>
+ *  <p><b>Do not use this class directly.</b> Instead configure <code>web.xml</code> to use the {@link
  * org.springframework.security.util.FilterToBeanProxy}.</p>
  *
  * @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;
+    }
 }

+ 8 - 5
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);
     }

+ 0 - 10
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());
         }
     }
 

+ 2 - 2
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
 
     <security:http createSession="ifRequired" pathType="ant" lowerCaseComparisons="true">
         <security:intercept-url pattern="/unprotected" filters="none" />
-        <security:intercept-url pattern="/somepath" access="ROLE_SPECIAL,ROLE_USER" />
+        <security:intercept-url pattern="/somepath" access="ROLE_SPECIAL,ROLE_USER" requiresChannel="http" />
         <security:intercept-url pattern="/**" access="ROLE_USER" />
 
         <!-- Default form login configuration. Will create filter and entry point -->
@@ -18,7 +18,7 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
         <security:http-basic realm="NamespaceTestRealm" />
 
         <!-- Default logout configuration -->
-        <security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />        
+        <security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />
 
         <security:concurrent-session-control maxSessions="1"/>