2
0
Эх сурвалжийг харах

Significantly enhance channel processing filter.

Ben Alex 21 жил өмнө
parent
commit
901c7d4752

+ 3 - 1
changelog.txt

@@ -1,4 +1,4 @@
-Changes in version 0.5 (2004-xx-xx)
+Changes in version 0.5 (2004-04-28)
 -----------------------------------
 
 * Added single sign on support via Yale Central Authentication Service (CAS)
@@ -13,7 +13,9 @@ Changes in version 0.5 (2004-xx-xx)
 * Added definable prefixes to avoid expectation of "ROLE_" GrantedAuthoritys
 * Added pluggable AuthenticationEntryPoints to SecurityEnforcementFilter
 * Added Apache Ant path syntax support to SecurityEnforcementFilter
+* Added filter to automate entry into secure channels, such as HTTPS
 * Updated JAR to Spring 1.0.1
+* Updated several classes to use absolute (not relative) redirection URLs
 * Refactored filters to use Spring application context lifecycle support
 * Improved constructor detection of nulls in User and other key objects
 * Fixed FilterInvocation.getRequestUrl() to also include getPathInfo()

+ 4 - 3
core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManager.java

@@ -29,10 +29,11 @@ public interface ChannelDecisionManager {
     //~ Methods ================================================================
 
     /**
-     * Decided whether the presented {@link FilterInvocation} provides
-     * sufficient security based on the requested {@link
+     * Decided whether the presented {@link FilterInvocation} provides the
+     * appropriate level of channel security based on the requested {@link
      * ConfigAttributeDefinition}.
      */
     public void decide(FilterInvocation invocation,
-        ConfigAttributeDefinition config) throws SecureChannelRequiredException;
+        ConfigAttributeDefinition config)
+        throws InsecureChannelRequiredException, SecureChannelRequiredException;
 }

+ 50 - 13
core/src/main/java/org/acegisecurity/securechannel/ChannelDecisionManagerImpl.java

@@ -26,14 +26,31 @@ import java.util.Iterator;
 
 /**
  * <p>
- * Requires a secure channel for a web request if a  {@link
- * ConfigAttribute#getAttribute()} keyword is detected.
+ * Ensures configuration attribute requested channel security is present by
+ * review of <code>HttpServletRequest.isSecure()</code> responses.
  * </p>
  * 
  * <P>
- * The default keyword string is <Code>REQUIRES_SECURE_CHANNEL</code>, but this
- * may be overriden to any value. The <code>ConfigAttribute</code> must
- * exactly match the case of the keyword string.
+ * The class responds to two and only two case-sensitive keywords: {@link
+ * #getInsecureKeyword()} and {@link #getSecureKeyword}. If either of these
+ * keywords are detected, <code>HttpServletRequest.isSecure()</code> is used
+ * to determine the channel security offered. If the channel security differs
+ * from that requested by the keyword, the relevant exception is thrown.
+ * </p>
+ * 
+ * <P>
+ * If both the <code>secureKeyword</code> and <code>insecureKeyword</code>
+ * configuration attributes are detected, the request will be deemed to be
+ * requesting a secure channel. This is a reasonable approach, as when in
+ * doubt, the decision manager assumes the most secure outcome is desired. Of
+ * course, you <b>should</b> indicate one configuration attribute or the other
+ * (not both).
+ * </p>
+ * 
+ * <P>
+ * The default <code>secureKeyword</code> and <code>insecureKeyword</code> is
+ * <code>REQUIRES_SECURE_CHANNEL</code> and
+ * <code>REQUIRES_INSECURE_CHANNEL</code> respectively.
  * </p>
  *
  * @author Ben Alex
@@ -43,21 +60,34 @@ public class ChannelDecisionManagerImpl implements InitializingBean,
     ChannelDecisionManager {
     //~ Instance fields ========================================================
 
-    private String keyword = "REQUIRES_SECURE_CHANNEL";
+    private String insecureKeyword = "REQUIRES_INSECURE_CHANNEL";
+    private String secureKeyword = "REQUIRES_SECURE_CHANNEL";
 
     //~ Methods ================================================================
 
-    public void setKeyword(String keyword) {
-        this.keyword = keyword;
+    public void setInsecureKeyword(String insecureKeyword) {
+        this.insecureKeyword = insecureKeyword;
+    }
+
+    public String getInsecureKeyword() {
+        return insecureKeyword;
     }
 
-    public String getKeyword() {
-        return keyword;
+    public void setSecureKeyword(String secureKeyword) {
+        this.secureKeyword = secureKeyword;
+    }
+
+    public String getSecureKeyword() {
+        return secureKeyword;
     }
 
     public void afterPropertiesSet() throws Exception {
-        if ((keyword == null) || "".equals(keyword)) {
-            throw new IllegalArgumentException("keyword required");
+        if ((secureKeyword == null) || "".equals(secureKeyword)) {
+            throw new IllegalArgumentException("secureKeyword required");
+        }
+
+        if ((insecureKeyword == null) || "".equals(insecureKeyword)) {
+            throw new IllegalArgumentException("insecureKeyword required");
         }
     }
 
@@ -72,12 +102,19 @@ public class ChannelDecisionManagerImpl implements InitializingBean,
         while (iter.hasNext()) {
             ConfigAttribute attribute = (ConfigAttribute) iter.next();
 
-            if (attribute.equals(keyword)) {
+            if (attribute.equals(secureKeyword)) {
                 if (!invocation.getHttpRequest().isSecure()) {
                     throw new SecureChannelRequiredException(
                         "Request is not being made over a secure channel");
                 }
             }
+
+            if (attribute.equals(insecureKeyword)) {
+                if (invocation.getHttpRequest().isSecure()) {
+                    throw new InsecureChannelRequiredException(
+                        "Request is being made over a secure channel when an insecure channel is required");
+                }
+            }
         }
     }
 }

+ 11 - 4
core/src/main/java/org/acegisecurity/securechannel/ChannelEntryPoint.java

@@ -23,7 +23,12 @@ import javax.servlet.ServletResponse;
 
 
 /**
- * Used by {@link ChannelProcessingFilter} to launch a secure web channel.
+ * Used by {@link ChannelProcessingFilter} to launch a web channel.
+ * 
+ * <P>
+ * Depending on the implementation, a secure or insecure channel will be
+ * launched.
+ * </p>
  *
  * @author Ben Alex
  * @version $Id$
@@ -37,12 +42,14 @@ public interface ChannelEntryPoint {
      * <P>
      * Implementations should modify the headers on the
      * <code>ServletResponse</code> as necessary to commence the user agent
-     * using the secure channel.
+     * using the implementation's supported channel type (ie secure or
+     * insecure).
      * </p>
      *
      * @param request that resulted in a
-     *        <code>SecureChannelRequiredException</code>
-     * @param response so that the user agent can begin using a secure channel
+     *        <code>SecureChannelRequiredException</code> or
+     *        <code>InsecureChannelRequiredException</code>
+     * @param response so that the user agent can begin using a new channel
      */
     public void commence(ServletRequest request, ServletResponse response)
         throws IOException, ServletException;

+ 45 - 18
core/src/main/java/org/acegisecurity/securechannel/ChannelProcessingFilter.java

@@ -37,7 +37,7 @@ import javax.servlet.http.HttpServletResponse;
 
 
 /**
- * Ensures a request is delivered over a secure channel.
+ * Ensures a web request is delivered over the required channel.
  * 
  * <p>
  * Internally uses a {@link FilterInvocation} to represent the request, so that
@@ -62,7 +62,8 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
     //~ Instance fields ========================================================
 
     private ChannelDecisionManager channelDecisionManager;
-    private ChannelEntryPoint channelEntryPoint;
+    private ChannelEntryPoint insecureChannelEntryPoint;
+    private ChannelEntryPoint secureChannelEntryPoint;
     private FilterInvocationDefinitionSource filterInvocationDefinitionSource;
 
     //~ Methods ================================================================
@@ -76,14 +77,6 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
         return channelDecisionManager;
     }
 
-    public void setChannelEntryPoint(ChannelEntryPoint channelEntryPoint) {
-        this.channelEntryPoint = channelEntryPoint;
-    }
-
-    public ChannelEntryPoint getChannelEntryPoint() {
-        return channelEntryPoint;
-    }
-
     public void setFilterInvocationDefinitionSource(
         FilterInvocationDefinitionSource filterInvocationDefinitionSource) {
         this.filterInvocationDefinitionSource = filterInvocationDefinitionSource;
@@ -93,6 +86,23 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
         return filterInvocationDefinitionSource;
     }
 
+    public void setInsecureChannelEntryPoint(
+        ChannelEntryPoint insecureChannelEntryPoint) {
+        this.insecureChannelEntryPoint = insecureChannelEntryPoint;
+    }
+
+    public ChannelEntryPoint getInsecureChannelEntryPoint() {
+        return insecureChannelEntryPoint;
+    }
+
+    public void setSecureChannelEntryPoint(ChannelEntryPoint channelEntryPoint) {
+        this.secureChannelEntryPoint = channelEntryPoint;
+    }
+
+    public ChannelEntryPoint getSecureChannelEntryPoint() {
+        return secureChannelEntryPoint;
+    }
+
     public void afterPropertiesSet() throws Exception {
         if (filterInvocationDefinitionSource == null) {
             throw new IllegalArgumentException(
@@ -104,9 +114,14 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
                 "channelDecisionManager must be specified");
         }
 
-        if (channelEntryPoint == null) {
+        if (secureChannelEntryPoint == null) {
+            throw new IllegalArgumentException(
+                "secureChannelEntryPoint must be specified");
+        }
+
+        if (insecureChannelEntryPoint == null) {
             throw new IllegalArgumentException(
-                "channelEntryPoint must be specified");
+                "insecureChannelEntryPoint must be specified");
         }
     }
 
@@ -128,20 +143,32 @@ public class ChannelProcessingFilter implements InitializingBean, Filter {
 
         if (attr != null) {
             if (logger.isDebugEnabled()) {
-                logger.debug("Request : " + request.toString()
+                logger.debug("Request: " + fi.getFullRequestUrl()
                     + "; ConfigAttributes: " + attr.toString());
             }
 
             try {
                 channelDecisionManager.decide(fi, attr);
-            } catch (SecureChannelRequiredException channelException) {
+            } catch (SecureChannelRequiredException secureException) {
                 if (logger.isDebugEnabled()) {
-                    logger.debug("Channel insufficient ("
-                        + channelException.getMessage()
-                        + "); delegating to channelEntryPoint");
+                    logger.debug("Channel insufficient security ("
+                        + secureException.getMessage()
+                        + "); delegating to secureChannelEntryPoint");
                 }
 
-                channelEntryPoint.commence(request, response);
+                secureChannelEntryPoint.commence(request, response);
+
+                return;
+            } catch (InsecureChannelRequiredException insecureException) {
+                if (logger.isDebugEnabled()) {
+                    logger.debug("Channel too much security ("
+                        + insecureException.getMessage()
+                        + "); delegating to insecureChannelEntryPoint");
+                }
+
+                insecureChannelEntryPoint.commence(request, response);
+
+                return;
             }
         }
 

+ 50 - 0
core/src/main/java/org/acegisecurity/securechannel/InsecureChannelRequiredException.java

@@ -0,0 +1,50 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.securechannel;
+
+import net.sf.acegisecurity.AccessDeniedException;
+
+
+/**
+ * Thrown if a secure web channel is detected, but is not required.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class InsecureChannelRequiredException extends AccessDeniedException {
+    //~ Constructors ===========================================================
+
+    /**
+     * Constructs an <code>InsecureChannelRequiredException</code> with the
+     * specified message.
+     *
+     * @param msg the detail message.
+     */
+    public InsecureChannelRequiredException(String msg) {
+        super(msg);
+    }
+
+    /**
+     * Constructs an <code>InsecureChannelRequiredException</code> with the
+     * specified message and root cause.
+     *
+     * @param msg the detail message.
+     * @param t root cause
+     */
+    public InsecureChannelRequiredException(String msg, Throwable t) {
+        super(msg, t);
+    }
+}

+ 120 - 0
core/src/main/java/org/acegisecurity/securechannel/RetryWithHttpEntryPoint.java

@@ -0,0 +1,120 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.securechannel;
+
+import net.sf.acegisecurity.util.PortMapper;
+import net.sf.acegisecurity.util.PortResolver;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+
+/**
+ * Commences an insecure channel by retrying the original request using HTTP.
+ * 
+ * <P>
+ * This entry point should suffice in most circumstances. However, it is not
+ * intended to properly handle HTTP POSTs or other usage where a standard
+ * redirect would cause an issue.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class RetryWithHttpEntryPoint implements InitializingBean,
+    ChannelEntryPoint {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(RetryWithHttpEntryPoint.class);
+
+    //~ Instance fields ========================================================
+
+    private PortMapper portMapper;
+    private PortResolver portResolver;
+
+    //~ Methods ================================================================
+
+    public void setPortMapper(PortMapper portMapper) {
+        this.portMapper = portMapper;
+    }
+
+    public PortMapper getPortMapper() {
+        return portMapper;
+    }
+
+    public void setPortResolver(PortResolver portResolver) {
+        this.portResolver = portResolver;
+    }
+
+    public PortResolver getPortResolver() {
+        return portResolver;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (portMapper == null) {
+            throw new IllegalArgumentException("portMapper is required");
+        }
+
+        if (portResolver == null) {
+            throw new IllegalArgumentException("portResolver is required");
+        }
+    }
+
+    public void commence(ServletRequest request, ServletResponse response)
+        throws IOException, ServletException {
+        HttpServletRequest req = (HttpServletRequest) request;
+
+        String pathInfo = req.getPathInfo();
+        String queryString = req.getQueryString();
+        String contextPath = req.getContextPath();
+        String destination = req.getServletPath()
+            + ((pathInfo == null) ? "" : pathInfo)
+            + ((queryString == null) ? "" : ("?" + queryString));
+
+        String redirectUrl = contextPath;
+
+        Integer httpsPort = new Integer(portResolver.getServerPort(req));
+        Integer httpPort = portMapper.lookupHttpPort(httpsPort);
+
+        if (httpPort != null) {
+            boolean includePort = true;
+
+            if (httpPort.intValue() == 80) {
+                includePort = false;
+            }
+
+            redirectUrl = "http://" + req.getServerName()
+                + ((includePort) ? (":" + httpPort) : "") + contextPath
+                + destination;
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Redirecting to: " + redirectUrl);
+        }
+
+        ((HttpServletResponse) response).sendRedirect(redirectUrl);
+    }
+}

+ 37 - 75
core/src/main/java/org/acegisecurity/securechannel/RetryWithHttpsEntryPoint.java

@@ -15,6 +15,9 @@
 
 package net.sf.acegisecurity.securechannel;
 
+import net.sf.acegisecurity.util.PortMapper;
+import net.sf.acegisecurity.util.PortResolver;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
@@ -22,10 +25,6 @@ import org.springframework.beans.factory.InitializingBean;
 
 import java.io.IOException;
 
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-
 import javax.servlet.ServletException;
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
@@ -53,71 +52,34 @@ public class RetryWithHttpsEntryPoint implements InitializingBean,
 
     //~ Instance fields ========================================================
 
-    private Map httpsPortMappings;
+    private PortMapper portMapper;
+    private PortResolver portResolver;
 
-    //~ Constructors ===========================================================
+    //~ Methods ================================================================
 
-    public RetryWithHttpsEntryPoint() {
-        httpsPortMappings = new HashMap();
-        httpsPortMappings.put(new Integer(80), new Integer(443));
-        httpsPortMappings.put(new Integer(8080), new Integer(8443));
+    public void setPortMapper(PortMapper portMapper) {
+        this.portMapper = portMapper;
     }
 
-    //~ Methods ================================================================
-
-    /**
-     * <p>
-     * Set to override the default http port to https port mappings of 80:443,
-     * and  8080:8443.
-     * </p>
-     * In a Spring XML ApplicationContext, a definition would look something
-     * like this:
-     * <pre>
-     *   &lt;property name="httpsPortMapping">
-     *     &lt;map>
-     *       &lt;entry key="80">&lt;value>443&lt;/value>&lt;/entry>
-     *       &lt;entry key="8080">&lt;value>8443&lt;/value>&lt;/entry>
-     *     &lt;/map>
-     *   &lt;/property>
-     * </pre>
-     *
-     * @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 corresponding integer https port number.
-     *
-     * @throws IllegalArgumentException if input map does not consist of String
-     *         keys and values, each representing an integer port number in
-     *         the range 1-65535 for that mapping.
-     */
-    public void setHttpsPortMappings(HashMap newMappings) {
-        httpsPortMappings.clear();
-
-        Iterator it = newMappings.entrySet().iterator();
-
-        while (it.hasNext()) {
-            Map.Entry entry = (Map.Entry) it.next();
-            Integer httpPort = new Integer((String) entry.getKey());
-            Integer httpsPort = new Integer((String) entry.getValue());
-
-            if ((httpPort.intValue() < 1) || (httpPort.intValue() > 65535)
-                || (httpsPort.intValue() < 1) || (httpsPort.intValue() > 65535)) {
-                throw new IllegalArgumentException(
-                    "one or both ports out of legal range: " + httpPort + ", "
-                    + httpsPort);
-            }
+    public PortMapper getPortMapper() {
+        return portMapper;
+    }
 
-            httpsPortMappings.put(httpPort, httpsPort);
+    public void setPortResolver(PortResolver portResolver) {
+        this.portResolver = portResolver;
+    }
 
-            if (httpsPortMappings.size() < 1) {
-                throw new IllegalArgumentException("must map at least one port");
-            }
-        }
+    public PortResolver getPortResolver() {
+        return portResolver;
     }
 
     public void afterPropertiesSet() throws Exception {
-        if (httpsPortMappings == null) {
-            throw new IllegalArgumentException("httpsPortMappings required");
+        if (portMapper == null) {
+            throw new IllegalArgumentException("portMapper is required");
+        }
+
+        if (portResolver == null) {
+            throw new IllegalArgumentException("portResolver is required");
         }
     }
 
@@ -134,25 +96,25 @@ public class RetryWithHttpsEntryPoint implements InitializingBean,
 
         String redirectUrl = contextPath;
 
-        Integer httpPort = new Integer(req.getServerPort());
-        Integer httpsPort = (Integer) httpsPortMappings.get(httpPort);
+        Integer httpPort = new Integer(portResolver.getServerPort(req));
+        Integer httpsPort = portMapper.lookupHttpsPort(httpPort);
 
         if (httpsPort != null) {
-            String serverName = req.getServerName();
-            redirectUrl = "https://" + serverName + ":" + httpsPort
-                + contextPath + destination;
+            boolean includePort = true;
+
+            if (httpsPort.intValue() == 443) {
+                includePort = false;
+            }
+
+            redirectUrl = "https://" + req.getServerName()
+                + ((includePort) ? (":" + httpsPort) : "") + contextPath
+                + destination;
         }
 
-        ((HttpServletResponse) response).sendRedirect(redirectUrl);
-    }
+        if (logger.isDebugEnabled()) {
+            logger.debug("Redirecting to: " + redirectUrl);
+        }
 
-    /**
-     * Returns the translated (Integer -> Integer) version of the original port
-     * mapping specified via setHttpsPortMapping()
-     *
-     * @return DOCUMENT ME!
-     */
-    protected Map getTranslatedHttpsPortMappings() {
-        return httpsPortMappings;
+        ((HttpServletResponse) response).sendRedirect(redirectUrl);
     }
 }

+ 182 - 0
core/src/test/java/org/acegisecurity/securechannel/ChannelDecisionManagerImplTests.java

@@ -0,0 +1,182 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.securechannel;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterChain;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.SecurityConfig;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+
+
+/**
+ * Tests {@link ChannelDecisionManagerImpl}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ChannelDecisionManagerImplTests extends TestCase {
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(ChannelDecisionManagerImplTests.class);
+    }
+
+    public void testDetectsInvalidInsecureKeyword() throws Exception {
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        cdm.setInsecureKeyword("");
+
+        try {
+            cdm.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("insecureKeyword required", expected.getMessage());
+        }
+
+        cdm.setInsecureKeyword(null);
+
+        try {
+            cdm.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("insecureKeyword required", expected.getMessage());
+        }
+    }
+
+    public void testDetectsInvalidSecureKeyword() throws Exception {
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        cdm.setSecureKeyword("");
+
+        try {
+            cdm.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("secureKeyword required", expected.getMessage());
+        }
+
+        cdm.setSecureKeyword(null);
+
+        try {
+            cdm.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("secureKeyword required", expected.getMessage());
+        }
+    }
+
+    public void testDetectsNullsPassedToMainMethod() {
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+
+        try {
+            cdm.decide(null, new ConfigAttributeDefinition());
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("Nulls cannot be provided", expected.getMessage());
+        }
+
+        try {
+            cdm.decide(new FilterInvocation(new MockHttpServletRequest("x"),
+                    new MockHttpServletResponse(), new MockFilterChain()), null);
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("Nulls cannot be provided", expected.getMessage());
+        }
+    }
+
+    public void testDetectsWhenInsecureChannelNeededAndInsecureSchemeUsed() {
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig(
+                "SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
+        attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
+
+        MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
+        request.setScheme("http");
+
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        cdm.decide(new FilterInvocation(request, new MockHttpServletResponse(),
+                new MockFilterChain()), attr);
+        assertTrue(true);
+    }
+
+    public void testDetectsWhenInsecureChannelNeededAndSecureSchemeUsed() {
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig(
+                "SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
+        attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
+
+        MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
+        request.setScheme("https");
+
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+
+        try {
+            cdm.decide(new FilterInvocation(request,
+                    new MockHttpServletResponse(), new MockFilterChain()), attr);
+        } catch (InsecureChannelRequiredException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testDetectsWhenSecureChannelNeeded() {
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig(
+                "SOME_CONFIG_ATTRIBUTE_TO_IGNORE"));
+        attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+
+        MockHttpServletRequest request = new MockHttpServletRequest("foo=bar");
+        request.setScheme("http");
+
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+
+        try {
+            cdm.decide(new FilterInvocation(request,
+                    new MockHttpServletResponse(), new MockFilterChain()), attr);
+        } catch (SecureChannelRequiredException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testGetterSetters() throws Exception {
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        cdm.afterPropertiesSet();
+        assertEquals("REQUIRES_INSECURE_CHANNEL", cdm.getInsecureKeyword());
+        assertEquals("REQUIRES_SECURE_CHANNEL", cdm.getSecureKeyword());
+
+        cdm.setInsecureKeyword("MY_INSECURE");
+        cdm.setSecureKeyword("MY_SECURE");
+
+        assertEquals("MY_INSECURE", cdm.getInsecureKeyword());
+        assertEquals("MY_SECURE", cdm.getSecureKeyword());
+    }
+
+    public void testIgnoresOtherConfigAttributes() {
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("XYZ"));
+
+        ChannelDecisionManagerImpl cdm = new ChannelDecisionManagerImpl();
+        cdm.decide(new FilterInvocation(new MockHttpServletRequest("x"),
+                new MockHttpServletResponse(), new MockFilterChain()), attr);
+        assertTrue(true);
+    }
+}
+;

+ 339 - 0
core/src/test/java/org/acegisecurity/securechannel/ChannelProcessingFilterTests.java

@@ -0,0 +1,339 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.securechannel;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.ConfigAttributeDefinition;
+import net.sf.acegisecurity.MockFilterConfig;
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.SecurityConfig;
+import net.sf.acegisecurity.intercept.web.FilterInvocation;
+import net.sf.acegisecurity.intercept.web.FilterInvocationDefinitionSource;
+import net.sf.acegisecurity.intercept.web.RegExpBasedFilterInvocationDefinitionMap;
+
+import java.io.IOException;
+
+import java.util.Iterator;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * Tests {@link ChannelProcessingFilter}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class ChannelProcessingFilterTests extends TestCase {
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(ChannelProcessingFilterTests.class);
+    }
+
+    public void testCallsInsecureEntryPointWhenTooMuchChannelSecurity()
+        throws Exception {
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("REQUIRES_INSECURE_CHANNEL"));
+
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr);
+
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new MockEntryPoint(true));
+        filter.setSecureChannelEntryPoint(new MockEntryPoint(false));
+        filter.setFilterInvocationDefinitionSource(fids);
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=now");
+        request.setServletPath("/path");
+        request.setScheme("https");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain(false);
+
+        filter.doFilter(request, response, chain);
+        assertTrue(true);
+    }
+
+    public void testCallsSecureEntryPointWhenTooLittleChannelSecurity()
+        throws Exception {
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr);
+
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new MockEntryPoint(false));
+        filter.setSecureChannelEntryPoint(new MockEntryPoint(true));
+        filter.setFilterInvocationDefinitionSource(fids);
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=now");
+        request.setServletPath("/path");
+        request.setScheme("http");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain(false);
+
+        filter.doFilter(request, response, chain);
+        assertTrue(true);
+    }
+
+    public void testDetectsMissingChannelDecisionManager()
+        throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
+        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
+
+        try {
+            filter.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("channelDecisionManager must be specified",
+                expected.getMessage());
+        }
+    }
+
+    public void testDetectsMissingFilterInvocationDefinitionMap()
+        throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
+        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        try {
+            filter.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("filterInvocationDefinitionSource must be specified",
+                expected.getMessage());
+        }
+    }
+
+    public void testDetectsMissingInsecureChannelEntryPoint()
+        throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
+        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        try {
+            filter.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("insecureChannelEntryPoint must be specified",
+                expected.getMessage());
+        }
+    }
+
+    public void testDetectsMissingSecureChannelEntryPoint()
+        throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
+        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        try {
+            filter.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("secureChannelEntryPoint must be specified",
+                expected.getMessage());
+        }
+    }
+
+    public void testDoFilterWithNonHttpServletRequestDetected()
+        throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+
+        try {
+            filter.doFilter(null, new MockHttpServletResponse(),
+                new MockFilterChain());
+            fail("Should have thrown ServletException");
+        } catch (ServletException expected) {
+            assertEquals("HttpServletRequest required", expected.getMessage());
+        }
+    }
+
+    public void testDoFilterWithNonHttpServletResponseDetected()
+        throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+
+        try {
+            filter.doFilter(new MockHttpServletRequest(null, null), null,
+                new MockFilterChain());
+            fail("Should have thrown ServletException");
+        } catch (ServletException expected) {
+            assertEquals("HttpServletResponse required", expected.getMessage());
+        }
+    }
+
+    public void testDoesNotInterruptRequestsWithCorrectChannelSecurity()
+        throws Exception {
+        ConfigAttributeDefinition attr = new ConfigAttributeDefinition();
+        attr.addConfigAttribute(new SecurityConfig("REQUIRES_SECURE_CHANNEL"));
+
+        MockFilterInvocationDefinitionMap fids = new MockFilterInvocationDefinitionMap("/path",
+                attr);
+
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
+        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
+        filter.setFilterInvocationDefinitionSource(fids);
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=now");
+        request.setServletPath("/path");
+        request.setScheme("https");
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain(true);
+
+        filter.doFilter(request, response, chain);
+        assertTrue(true);
+    }
+
+    public void testDoesNotInterruptRequestsWithNoConfigAttribute()
+        throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
+        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
+        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        MockHttpServletRequest request = new MockHttpServletRequest("info=now");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+        MockFilterChain chain = new MockFilterChain(true);
+
+        filter.doFilter(request, response, chain);
+        assertTrue(true);
+    }
+
+    public void testGetterSetters() {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
+        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
+        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+
+        assertTrue(filter.getInsecureChannelEntryPoint() != null);
+        assertTrue(filter.getSecureChannelEntryPoint() != null);
+        assertTrue(filter.getFilterInvocationDefinitionSource() != null);
+        assertTrue(filter.getChannelDecisionManager() != null);
+    }
+
+    public void testLifecycle() throws Exception {
+        ChannelProcessingFilter filter = new ChannelProcessingFilter();
+        filter.setInsecureChannelEntryPoint(new RetryWithHttpEntryPoint());
+        filter.setSecureChannelEntryPoint(new RetryWithHttpsEntryPoint());
+        filter.setFilterInvocationDefinitionSource(new RegExpBasedFilterInvocationDefinitionMap());
+        filter.setChannelDecisionManager(new ChannelDecisionManagerImpl());
+        filter.afterPropertiesSet();
+
+        filter.init(new MockFilterConfig());
+        filter.destroy();
+    }
+
+    //~ Inner Classes ==========================================================
+
+    private class MockEntryPoint implements ChannelEntryPoint {
+        private boolean expectToBeCalled;
+
+        public MockEntryPoint(boolean expectToBeCalled) {
+            this.expectToBeCalled = expectToBeCalled;
+        }
+
+        private MockEntryPoint() {
+            super();
+        }
+
+        public void commence(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException {
+            if (expectToBeCalled) {
+                assertTrue(true);
+            } else {
+                fail("Did not expect this ChannelEntryPoint to be called");
+            }
+        }
+    }
+
+    private class MockFilterChain implements FilterChain {
+        private boolean expectToProceed;
+
+        public MockFilterChain(boolean expectToProceed) {
+            this.expectToProceed = expectToProceed;
+        }
+
+        private MockFilterChain() {
+            super();
+        }
+
+        public void doFilter(ServletRequest request, ServletResponse response)
+            throws IOException, ServletException {
+            if (expectToProceed) {
+                assertTrue(true);
+            } else {
+                fail("Did not expect filter chain to proceed");
+            }
+        }
+    }
+
+    private class MockFilterInvocationDefinitionMap
+        implements FilterInvocationDefinitionSource {
+        private ConfigAttributeDefinition toReturn;
+        private String servletPath;
+
+        public MockFilterInvocationDefinitionMap(String servletPath,
+            ConfigAttributeDefinition toReturn) {
+            this.servletPath = servletPath;
+            this.toReturn = toReturn;
+        }
+
+        private MockFilterInvocationDefinitionMap() {
+            super();
+        }
+
+        public ConfigAttributeDefinition getAttributes(Object object)
+            throws IllegalArgumentException {
+            FilterInvocation fi = (FilterInvocation) object;
+
+            if (servletPath.equals(fi.getHttpRequest().getServletPath())) {
+                return toReturn;
+            } else {
+                return null;
+            }
+        }
+
+        public Iterator getConfigAttributeDefinitions() {
+            return null;
+        }
+
+        public boolean supports(Class clazz) {
+            return true;
+        }
+    }
+}

+ 171 - 0
core/src/test/java/org/acegisecurity/securechannel/RetryWithHttpEntryPointTests.java

@@ -0,0 +1,171 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.securechannel;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.MockPortResolver;
+import net.sf.acegisecurity.util.PortMapperImpl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Tests {@link RetryWithHttpEntryPoint}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class RetryWithHttpEntryPointTests extends TestCase {
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(RetryWithHttpEntryPointTests.class);
+    }
+
+    public void testDetectsMissingPortMapper() throws Exception {
+        RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
+        ep.setPortResolver(new MockPortResolver(80, 443));
+
+        try {
+            ep.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("portMapper is required", expected.getMessage());
+        }
+    }
+
+    public void testDetectsMissingPortResolver() throws Exception {
+        RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+
+        try {
+            ep.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("portResolver is required", expected.getMessage());
+        }
+    }
+
+    public void testGettersSetters() {
+        RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(8080, 8443));
+        assertTrue(ep.getPortMapper() != null);
+        assertTrue(ep.getPortResolver() != null);
+    }
+
+    public void testNormalOperation() throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest("open=true");
+        request.setScheme("https");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo("/pathInfo.html");
+        request.setServerPort(443);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(80, 443));
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("http://www.example.com/bigWebApp/hello/pathInfo.html?open=true",
+            response.getRedirect());
+    }
+
+    public void testNormalOperationWithNullPathInfoAndNullQueryString()
+        throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest(null);
+        request.setScheme("https");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo(null);
+        request.setServerPort(443);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(80, 443));
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("http://www.example.com/bigWebApp/hello",
+            response.getRedirect());
+    }
+
+    public void testOperationWhenTargetPortIsUnknown()
+        throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest("open=true");
+        request.setScheme("https");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo("/pathInfo.html");
+        request.setServerPort(8768);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(8768, 1234));
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("/bigWebApp", response.getRedirect());
+    }
+
+    public void testOperationWithNonStandardPort() throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest("open=true");
+        request.setScheme("https");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo("/pathInfo.html");
+        request.setServerPort(9999);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        PortMapperImpl portMapper = new PortMapperImpl();
+        Map map = new HashMap();
+        map.put("8888", "9999");
+        portMapper.setPortMappings(map);
+
+        RetryWithHttpEntryPoint ep = new RetryWithHttpEntryPoint();
+        ep.setPortResolver(new MockPortResolver(8888, 9999));
+        ep.setPortMapper(portMapper);
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("http://www.example.com:8888/bigWebApp/hello/pathInfo.html?open=true",
+            response.getRedirect());
+    }
+}

+ 171 - 0
core/src/test/java/org/acegisecurity/securechannel/RetryWithHttpsEntryPointTests.java

@@ -0,0 +1,171 @@
+/* Copyright 2004 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.securechannel;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+import net.sf.acegisecurity.MockPortResolver;
+import net.sf.acegisecurity.util.PortMapperImpl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * Tests {@link RetryWithHttpsEntryPoint}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class RetryWithHttpsEntryPointTests extends TestCase {
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(RetryWithHttpsEntryPointTests.class);
+    }
+
+    public void testDetectsMissingPortMapper() throws Exception {
+        RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
+        ep.setPortResolver(new MockPortResolver(80, 443));
+
+        try {
+            ep.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("portMapper is required", expected.getMessage());
+        }
+    }
+
+    public void testDetectsMissingPortResolver() throws Exception {
+        RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+
+        try {
+            ep.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("portResolver is required", expected.getMessage());
+        }
+    }
+
+    public void testGettersSetters() {
+        RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(8080, 8443));
+        assertTrue(ep.getPortMapper() != null);
+        assertTrue(ep.getPortResolver() != null);
+    }
+
+    public void testNormalOperation() throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest("open=true");
+        request.setScheme("http");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo("/pathInfo.html");
+        request.setServerPort(80);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(80, 443));
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("https://www.example.com/bigWebApp/hello/pathInfo.html?open=true",
+            response.getRedirect());
+    }
+
+    public void testNormalOperationWithNullPathInfoAndNullQueryString()
+        throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest(null);
+        request.setScheme("http");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo(null);
+        request.setServerPort(80);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(80, 443));
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("https://www.example.com/bigWebApp/hello",
+            response.getRedirect());
+    }
+
+    public void testOperationWhenTargetPortIsUnknown()
+        throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest("open=true");
+        request.setScheme("http");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo("/pathInfo.html");
+        request.setServerPort(8768);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
+        ep.setPortMapper(new PortMapperImpl());
+        ep.setPortResolver(new MockPortResolver(8768, 1234));
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("/bigWebApp", response.getRedirect());
+    }
+
+    public void testOperationWithNonStandardPort() throws Exception {
+        MockHttpServletRequest request = new MockHttpServletRequest("open=true");
+        request.setScheme("http");
+        request.setServerName("www.example.com");
+        request.setContextPath("/bigWebApp");
+        request.setServletPath("/hello");
+        request.setPathInfo("/pathInfo.html");
+        request.setServerPort(8888);
+
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        PortMapperImpl portMapper = new PortMapperImpl();
+        Map map = new HashMap();
+        map.put("8888", "9999");
+        portMapper.setPortMappings(map);
+
+        RetryWithHttpsEntryPoint ep = new RetryWithHttpsEntryPoint();
+        ep.setPortResolver(new MockPortResolver(8888, 9999));
+        ep.setPortMapper(portMapper);
+        ep.afterPropertiesSet();
+
+        ep.commence(request, response);
+        System.out.println(response.getRedirect());
+        assertEquals("https://www.example.com:9999/bigWebApp/hello/pathInfo.html?open=true",
+            response.getRedirect());
+    }
+}

+ 271 - 23
docs/reference/src/index.xml

@@ -519,33 +519,23 @@
 
         <para>Notice that the filter is actually a
         <literal>FilterToBeanProxy</literal>. Most of the filters used by the
-        Acegi Security System for Spring use this class . What it does is
-        delegate the <literal>Filter</literal>'s methods through to a bean
-        which is obtained from the Spring application context. This enables
-        the bean to benefit from the Spring application context lifecycle
-        support and configuration flexibility.
-        <literal>FilterToBeanProxy</literal> only requires a single
-        initialization parameter, <literal>targetClass</literal> or
-        <literal>targetBean</literal>. The <literal>targetClass</literal>
-        parameter locates the first object in the application context of the
-        specified class, whilst <literal>targetBean</literal> locates the
-        object by bean name. Like standard Spring web applications, the
-        <literal>FilterToBeanProxy</literal> accesses the application context
-        via<literal>
-        WebApplicationContextUtils.getWebApplicationContext(ServletContext)</literal>,
-        so you should configure a <literal>ContextLoaderListener</literal> in
-        <literal>web.xml</literal>.</para>
+        Acegi Security System for Spring use this class. Refer to the Filters
+        section to learn more about this bean.</para>
 
-        <para>In the application context you will need to configure three
+        <para>In the application context you will need to configure four
         beans:</para>
 
         <programlisting>&lt;bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"&gt;
   &lt;property name="filterSecurityInterceptor"&gt;&lt;ref bean="filterInvocationInterceptor"/&gt;&lt;/property&gt;
   &lt;property name="authenticationEntryPoint"&gt;&lt;ref bean="authenticationEntryPoint"/&gt;&lt;/property&gt;
+  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
 &lt;/bean&gt;
 
 &lt;bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint"&gt;
   &lt;property name="loginFormUrl"&gt;&lt;value&gt;/acegilogin.jsp&lt;/value&gt;&lt;/property&gt;
+  &lt;property name="forceHttps"&gt;&lt;value&gt;false&lt;/value&gt;&lt;/property&gt;
+  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
+  &lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
 &lt;/bean&gt;
 
 &lt;bean id="filterInvocationInterceptor" class="net.sf.acegisecurity.intercept.web.FilterSecurityInterceptor"&gt;
@@ -559,6 +549,12 @@
       \A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_TELLER
     &lt;/value&gt;
   &lt;/property&gt;
+&lt;/bean&gt;
+
+&lt;!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() --&gt;
+&lt;bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl"&gt;
+  &lt;property name="alwaysHttpPort"&gt;&lt;value&gt;8080&lt;/value&gt;&lt;/property&gt;
+  &lt;property name="alwaysHttpsPort"&gt;&lt;value&gt;8443&lt;/value&gt;&lt;/property&gt;
 &lt;/bean&gt;</programlisting>
 
         <para>The <literal>AuthenticationEntryPoint</literal> will be called
@@ -577,6 +573,21 @@
         properties related to forcing the use of HTTPS, so please refer to the
         JavaDocs if you require this.</para>
 
+        <para>The <literal>PortResolver</literal> is used to inspect a HTTP
+        request and determine the server port it was received on. Generally
+        this means using <literal>ServletRequest.getServerPort()</literal>,
+        although implementations can be forced to always return particular
+        ports (based on the transport protocol), as shown in the example
+        above. </para>
+
+        <para>The <literal>PortMapper</literal> provides information on which
+        HTTPS ports correspond to which HTTP ports. This is used by the
+        <literal>AuthenticationProcessingFilterEntryPoint</literal> and
+        several other beans. The default implementation,
+        <literal>PortMapperImpl</literal>, knows the common HTTP ports 80 and
+        8080 map to HTTPS ports 443 and 8443 respectively. You can customise
+        this mapping if desired.</para>
+
         <para>The <literal>SecurityEnforcementFilter</literal> primarily
         provides session management support and initiates authentication when
         required. It delegates actual <literal>FilterInvocation</literal>
@@ -1585,9 +1596,8 @@ public boolean supports(Class clazz);</programlisting></para>
 &lt;/filter-mapping&gt;</programlisting></para>
 
         <para>For a discussion of <literal>FilterToBeanProxy</literal>, please
-        refer to the FilterInvocation Security Interceptor section. The
-        application context will need to define the
-        <literal>AuthenticationProcessingFilter</literal>:</para>
+        refer to the Filters section. The application context will need to
+        define the <literal>AuthenticationProcessingFilter</literal>:</para>
 
         <para><programlisting>&lt;bean id="authenticationProcessingFilter" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilter"&gt;
   &lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
@@ -1661,9 +1671,8 @@ public boolean supports(Class clazz);</programlisting></para>
 &lt;/filter-mapping&gt;</programlisting></para>
 
         <para>For a discussion of <literal>FilterToBeanProxy</literal>, please
-        refer to the FilterInvocation Security Interceptor section. The
-        application context will need to define the
-        <literal>BasicProcessingFilter</literal> and its required
+        refer to the Filters section. The application context will need to
+        define the <literal>BasicProcessingFilter</literal> and its required
         collaborator:</para>
 
         <para><programlisting>&lt;bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"&gt;
@@ -2739,6 +2748,245 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
       </sect2>
     </sect1>
 
+    <sect1 id="security-channels">
+      <title>Channel Security</title>
+
+      <sect2 id="security-channels-overview">
+        <title>Overview</title>
+
+        <para>In addition to coordinating the authentication and authorization
+        requirements of your application, the Acegi Security System for Spring
+        is also able to ensure web requests are received using an appropriate
+        transport. If your application has many security requirements, you'll
+        probably want to use HTTPS as the transport, whilst less secure pages
+        can use the unencrypted HTTP transport.</para>
+
+        <para>An important issue in considering transport security is that of
+        session hijacking. Your web container manages a
+        <literal>HttpSession</literal> by reference to a
+        <literal>jsessionid</literal> that is sent to user agents either via a
+        cookie or URL rewriting. If the <literal>jsessionid</literal> is ever
+        sent over HTTP, there is a possibility that session identifier can be
+        intercepted and used to impersonate the user after they complete the
+        authentication process. This is because most web containers maintain
+        the same session identifier for a given user, even after they switch
+        from HTTP to HTTPS pages.</para>
+
+        <para>If session hijacking is considered too significant a risk for
+        your particular application, the only option is to use HTTPS for every
+        request. This means the <literal>jsessionid</literal> is never sent
+        across an insecure channel. You will need to ensure your
+        <literal>web.xml</literal>-defined
+        <literal>&lt;welcome-file&gt;</literal> points to a HTTPS location,
+        and the application never directs the user to a HTTP location. The
+        Acegi Security System for Spring provides a solution to assist with
+        the latter.</para>
+      </sect2>
+
+      <sect2 id="security-channels-installation">
+        <title>Configuration</title>
+
+        <para>To utilise Acegi Security's channel security services, add the
+        following lines to <literal>web.xml</literal>:</para>
+
+        <para><programlisting>&lt;filter&gt;
+  &lt;filter-name&gt;Acegi Channel Processing Filter&lt;/filter-name&gt;
+  &lt;filter-class&gt;net.sf.acegisecurity.util.FilterToBeanProxy&lt;/filter-class&gt;
+  &lt;init-param&gt;
+    &lt;param-name&gt;targetClass&lt;/param-name&gt;
+    &lt;param-value&gt;net.sf.acegisecurity.securechannel.ChannelProcessingFilter&lt;/param-value&gt;
+  &lt;/init-param&gt;
+&lt;/filter&gt;
+
+&lt;filter-mapping&gt;
+  &lt;filter-name&gt;Acegi Channel Processing Filter&lt;/filter-name&gt;
+  &lt;url-pattern&gt;/*&lt;/url-pattern&gt;
+&lt;/filter-mapping&gt;</programlisting></para>
+
+        <para>As usual when running <literal>FilterToBeanProxy</literal>, you
+        will also need to configure the filter in your application
+        context:</para>
+
+        <para><programlisting>&lt;bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter"&gt;
+  &lt;property name="channelDecisionManager"&gt;&lt;ref bean="channelDecisionManager"/&gt;&lt;/property&gt;
+  &lt;property name="secureChannelEntryPoint"&gt;&lt;ref bean="secureChannelEntryPoint"/&gt;&lt;/property&gt;
+  &lt;property name="insecureChannelEntryPoint"&gt;&lt;ref bean="insecureChannelEntryPoint"/&gt;&lt;/property&gt;
+  &lt;property name="filterInvocationDefinitionSource"&gt;
+    &lt;value&gt;
+      CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+      \A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
+      \A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL
+      \A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL
+      \A.*\Z=REQUIRES_INSECURE_CHANNEL
+    &lt;/value&gt;
+  &lt;/property&gt;
+&lt;/bean&gt;
+
+&lt;bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/&gt;
+
+&lt;bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint"&gt;
+  &lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
+  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
+&lt;/bean&gt;
+
+&lt;bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint"&gt;
+  &lt;property name="portMapper"&gt;&lt;ref bean="portMapper"/&gt;&lt;/property&gt;
+  &lt;property name="portResolver"&gt;&lt;ref bean="portResolver"/&gt;&lt;/property&gt;
+&lt;/bean&gt;</programlisting></para>
+
+        <para>Like <literal>FilterSecurityInterceptor</literal>, Apache Ant
+        style paths are also supported by the
+        <literal>ChannelProcessingFilter</literal>.</para>
+
+        <para>The <literal>ChannelProcessingFilter</literal> operates by
+        filtering all web requests and determining the configuration
+        attributes that apply. It then delegates to the
+        <literal>ChannelDecisionManager</literal>. The default implementation,
+        <literal>ChannelDecisionManagerImpl</literal>, should suffice in most
+        cases. It simply throws a
+        <literal>SecureChannelRequiredException</literal> or
+        <literal>InsecureChannelRequiredException</literal> if the request's
+        transport channel carries too little or too much security
+        respectively. </para>
+
+        <para>The <literal>ChannelProcessingFilter</literal> will detect the
+        <literal>SecureChannelRequiredException</literal> or
+        <literal>InsecureChannelRequiredException</literal> and delegate to
+        the <literal>secureChannelEntryPoint</literal> or
+        <literal>insecureChannelEntryPoint</literal> respectively. These entry
+        points implement the <literal>ChannelEntryPoint</literal> interface,
+        which allows the implementation to perform a redirect or take similar
+        action. The included <literal>RetryWithHttpsEntryPoint</literal> and
+        <literal>RetryWithHttpEntryPoint</literal> implementations simply
+        perform a redirect.</para>
+
+        <para>Note that the redirections are absolute (eg
+        http://www.company.com:8080/app/page), not relative (eg /app/page).
+        During testing it was discovered that Internet Explorer 6 Service Pack
+        1 appears to have a bug whereby it does not respond correctly to a
+        redirection instruction which also changes the port to use.
+        Accordingly, absolute URLs are used in conjunction with the
+        <literal>PortResolver</literal> interface to overcome this issue. The
+        <literal>PortResolverImpl</literal> is the included implementation,
+        and is capable of determining the port a request was received on
+        either from the <literal>ServletRequest.getServerPort()</literal>
+        method or from properties defined in the application context. Please
+        refer to the JavaDocs for <literal>PortResolverImpl</literal> for
+        further details.</para>
+      </sect2>
+
+      <sect2 id="security-channels-usage">
+        <title>Usage</title>
+
+        <para>Once configured, using the channel security filter is very easy.
+        Simply request pages without regard to the protocol (ie HTTP or HTTPS)
+        or port (eg 80, 8080, 443, 8443 etc). Obviously you'll still need a
+        way of making the initial request (probably via the
+        <literal>web.xml</literal> <literal>&lt;welcome-file&gt;</literal> or
+        a well-known home page URL), but once this is done the filter will
+        perform redirects as defined by your application context.</para>
+      </sect2>
+    </sect1>
+
+    <sect1 id="security-filters">
+      <title>Filters</title>
+
+      <sect2 id="security-filters-overview">
+        <title>Overview</title>
+
+        <para>The Acegi Security System for Spring uses filters extensively.
+        Each filter is covered in detail in a respective section of this
+        document. This section includes information that applies to all
+        filters.</para>
+      </sect2>
+
+      <sect2 id="security-filters-filtertobeanproxy">
+        <title>FilterToBeanProxy</title>
+
+        <para>Most filters are configured using the
+        <literal>FilterToBeanProxy</literal>. An example configuration from
+        <literal>web.xml</literal> follows:</para>
+
+        <para><programlisting>&lt;filter&gt;
+  &lt;filter-name&gt;Acegi HTTP Request Security Filter&lt;/filter-name&gt;
+  &lt;filter-class&gt;net.sf.acegisecurity.util.FilterToBeanProxy&lt;/filter-class&gt;
+  &lt;init-param&gt;
+    &lt;param-name&gt;targetClass&lt;/param-name&gt;
+    &lt;param-value&gt;net.sf.acegisecurity.ClassThatImplementsFilter&lt;/param-value&gt;
+  &lt;/init-param&gt;
+&lt;/filter&gt;</programlisting></para>
+
+        <para>Notice that the filter in <literal>web.xml</literal> is actually
+        a <literal>FilterToBeanProxy</literal>, and not the filter that will
+        actually implements the logic of the filter. What
+        <literal>FilterToBeanProxy</literal> does is delegate the
+        <literal>Filter</literal>'s methods through to a bean which is
+        obtained from the Spring application context. This enables the bean to
+        benefit from the Spring application context lifecycle support and
+        configuration flexibility. The bean must implement
+        <literal>javax.servlet.Filter</literal>.</para>
+
+        <para>The <literal>FilterToBeanProxy</literal> only requires a single
+        initialization parameter, <literal>targetClass</literal> or
+        <literal>targetBean</literal>. The <literal>targetClass</literal>
+        parameter locates the first object in the application context of the
+        specified class, whilst <literal>targetBean</literal> locates the
+        object by bean name. Like standard Spring web applications, the
+        <literal>FilterToBeanProxy</literal> accesses the application context
+        via<literal>
+        WebApplicationContextUtils.getWebApplicationContext(ServletContext)</literal>,
+        so you should configure a <literal>ContextLoaderListener</literal> in
+        <literal>web.xml</literal>.</para>
+      </sect2>
+
+      <sect2 id="security-filters-order">
+        <title>Filter Ordering</title>
+
+        <para>The order that filters are defined in <literal>web.xml</literal>
+        is important.</para>
+
+        <para>Irrespective of which filters you are actually using, the order
+        of the <literal>&lt;filter-mapping&gt;</literal>s should be as
+        follows:</para>
+
+        <orderedlist>
+          <listitem>
+            <para>Acegi Channel Processing Filter
+            (<literal>ChannelProcessingFilter</literal>)</para>
+          </listitem>
+
+          <listitem>
+            <para>Acegi Authentication Processing Filter
+            (<literal>AuthenticationProcessingFilter</literal>)</para>
+          </listitem>
+
+          <listitem>
+            <para>Acegi CAS Processing Filter
+            (<literal>CasProcessingFilter</literal>)</para>
+          </listitem>
+
+          <listitem>
+            <para>Acegi HTTP BASIC Authorization Filter
+            (<literal>BasicProcessingFilter</literal>)</para>
+          </listitem>
+
+          <listitem>
+            <para>Acegi Security System for Spring Auto Integration Filter
+            (<literal>AutoIntegrationFilter</literal>)</para>
+          </listitem>
+
+          <listitem>
+            <para>Acegi HTTP Request Security Filter
+            (<literal>SecurityEnforcementFilter</literal>)</para>
+          </listitem>
+        </orderedlist>
+
+        <para>All of the above filters use
+        <literal>FilterToBeanProxy</literal>, which is discussed in the
+        previous section.</para>
+      </sect2>
+    </sect1>
+
     <sect1 id="security-sample">
       <title>Contacts Sample Application</title>
 

+ 37 - 0
samples/contacts/etc/cas/applicationContext.xml

@@ -159,6 +159,34 @@
 
 	<bean id="backendContactManagerTarget" class="sample.contact.ContactManagerBackend"/>
 
+	<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
+	
+	<bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter">
+		<property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
+		<property name="secureChannelEntryPoint"><ref bean="secureChannelEntryPoint"/></property>
+		<property name="insecureChannelEntryPoint"><ref bean="insecureChannelEntryPoint"/></property>
+ 		<property name="filterInvocationDefinitionSource">
+			<value>
+			    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
+				\A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
+				\A/j_acegi_cas_security_check.*\Z=REQUIRES_SECURE_CHANNEL	
+				\A.*\Z=REQUIRES_INSECURE_CHANNEL
+			</value>
+		</property>
+	</bean>
+
+	<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/>
+
+	<bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint">
+		<property name="portMapper"><ref bean="portMapper"/></property>
+		<property name="portResolver"><ref bean="portResolver"/></property>
+	</bean>
+
+	<bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint">
+		<property name="portMapper"><ref bean="portMapper"/></property>
+		<property name="portResolver"><ref bean="portResolver"/></property>
+	</bean>
+
 	<!-- ===================== HTTP REQUEST SECURITY ==================== -->
 
 	<bean id="casProcessingFilter" class="net.sf.acegisecurity.ui.cas.CasProcessingFilter">
@@ -171,6 +199,7 @@
 	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
 		<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
 		<property name="authenticationEntryPoint"><ref bean="casProcessingFilterEntryPoint"/></property>
+		<property name="portResolver"><ref bean="portResolver"/></property>
 	</bean>
 
 	<bean id="casProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.cas.CasProcessingFilterEntryPoint">
@@ -178,6 +207,14 @@
 		<property name="serviceProperties"><ref bean="serviceProperties"/></property>
 	</bean>
 
+	<bean id="portMapper" class="net.sf.acegisecurity.util.PortMapperImpl"/>
+	
+	<!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() -->
+	<bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl">
+		<property name="alwaysHttpPort"><value>8080</value></property>
+		<property name="alwaysHttpsPort"><value>8443</value></property>
+	</bean>
+
 	<bean id="httpRequestAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">
    		<property name="allowIfAllAbstainDecisions"><value>false</value></property>
 		<property name="decisionVoters">

+ 14 - 0
samples/contacts/etc/cas/web.xml

@@ -33,6 +33,15 @@
 		<param-value>/WEB-INF/applicationContext.xml</param-value>
 	</context-param>
 
+   <filter>
+        <filter-name>Acegi Channel Processing Filter</filter-name>
+        <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
+        <init-param>
+            <param-name>targetClass</param-name>
+            <param-value>net.sf.acegisecurity.securechannel.ChannelProcessingFilter</param-value>
+        </init-param>
+    </filter>
+
     <filter>
         <filter-name>Acegi CAS Processing Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
@@ -65,6 +74,11 @@
         </init-param>
     </filter>
 
+    <filter-mapping>
+      <filter-name>Acegi Channel Processing Filter</filter-name>
+      <url-pattern>/*</url-pattern>
+    </filter-mapping>
+
     <filter-mapping>
       <filter-name>Acegi CAS Processing Filter</filter-name>
       <url-pattern>/*</url-pattern>

+ 28 - 3
samples/contacts/etc/filter/applicationContext.xml

@@ -134,23 +134,36 @@
 	<bean id="backendContactManagerTarget" class="sample.contact.ContactManagerBackend"/>
 
 	<!-- ===================== HTTP CHANNEL REQUIREMENTS ==================== -->
+	
+	<!-- You will need to uncomment the "Acegi Channel Processing Filter"
+	     <filter-mapping> in web.xml for the following beans to be used -->
 
 	<bean id="channelProcessingFilter" class="net.sf.acegisecurity.securechannel.ChannelProcessingFilter">
 		<property name="channelDecisionManager"><ref bean="channelDecisionManager"/></property>
-		<property name="channelEntryPoint"><ref bean="channelEntryPoint"/></property>
+		<property name="secureChannelEntryPoint"><ref bean="secureChannelEntryPoint"/></property>
+		<property name="insecureChannelEntryPoint"><ref bean="insecureChannelEntryPoint"/></property>
  		<property name="filterInvocationDefinitionSource">
 			<value>
 			    CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
 				\A/secure/.*\Z=REQUIRES_SECURE_CHANNEL
-				\A/info/.*\Z=REQUIRES_SECURE_CHANNEL
 				\A/acegilogin.jsp.*\Z=REQUIRES_SECURE_CHANNEL
+				\A/j_acegi_security_check.*\Z=REQUIRES_SECURE_CHANNEL	
+				\A.*\Z=REQUIRES_INSECURE_CHANNEL
 			</value>
 		</property>
 	</bean>
 
 	<bean id="channelDecisionManager" class="net.sf.acegisecurity.securechannel.ChannelDecisionManagerImpl"/>
 
-	<bean id="channelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint"/>
+	<bean id="secureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpsEntryPoint">
+		<property name="portMapper"><ref bean="portMapper"/></property>
+		<property name="portResolver"><ref bean="portResolver"/></property>
+	</bean>
+
+	<bean id="insecureChannelEntryPoint" class="net.sf.acegisecurity.securechannel.RetryWithHttpEntryPoint">
+		<property name="portMapper"><ref bean="portMapper"/></property>
+		<property name="portResolver"><ref bean="portResolver"/></property>
+	</bean>
 
 	<!-- ===================== HTTP REQUEST SECURITY ==================== -->
 
@@ -164,10 +177,22 @@
 	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
 		<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
 		<property name="authenticationEntryPoint"><ref bean="authenticationProcessingFilterEntryPoint"/></property>
+		<property name="portResolver"><ref bean="portResolver"/></property>
 	</bean>
 
 	<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
 		<property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
+		<property name="forceHttps"><value>false</value></property>
+		<property name="portMapper"><ref bean="portMapper"/></property>
+		<property name="portResolver"><ref bean="portResolver"/></property>
+	</bean>
+
+	<bean id="portMapper" class="net.sf.acegisecurity.util.PortMapperImpl"/>
+	
+	<!-- Comment the always[Scheme]Port properties to use ServletRequest.getServerPort() -->
+	<bean id="portResolver" class="net.sf.acegisecurity.util.PortResolverImpl">
+		<property name="alwaysHttpPort"><value>8080</value></property>
+		<property name="alwaysHttpsPort"><value>8443</value></property>
 	</bean>
 
 	<bean id="httpRequestAccessDecisionManager" class="net.sf.acegisecurity.vote.AffirmativeBased">

+ 9 - 5
samples/contacts/etc/filter/web.xml

@@ -23,8 +23,8 @@
 		<param-name>contextConfigLocation</param-name>
 		<param-value>/WEB-INF/applicationContext.xml</param-value>
 	</context-param>
-
-    <filter>
+	
+   <filter>
         <filter-name>Acegi Channel Processing Filter</filter-name>
         <filter-class>net.sf.acegisecurity.util.FilterToBeanProxy</filter-class>
         <init-param>
@@ -64,12 +64,16 @@
             <param-value>net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value>
         </init-param>
     </filter>
-
+	
+	<!-- Remove the comments from the following <filter-mapping> if you'd
+	     like to ensure secure URLs are only available over HTTPS -->
+    <!--
     <filter-mapping>
       <filter-name>Acegi Channel Processing Filter</filter-name>
       <url-pattern>/*</url-pattern>
     </filter-mapping>
-
+	-->
+	
     <filter-mapping>
       <filter-name>Acegi Authentication Processing Filter</filter-name>
       <url-pattern>/*</url-pattern>
@@ -99,7 +103,7 @@
 	<listener>
 		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 	</listener>
-
+	
   <!--
     - Servlet that dispatches request to registered handlers (Controller implementations).
     - Has its own application context, by default defined in "{servlet-name}-servlet.xml",