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

Added building of filter chain in post-processing, support for basic authentication and automatic generation of login page, if no loginUrl supplied.

Luke Taylor 18 жил өмнө
parent
commit
cffd3131f0

+ 43 - 0
core/src/main/java/org/springframework/security/config/BasicAuthenticationBeanDefinitionParser.java

@@ -0,0 +1,43 @@
+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.RootBeanDefinition;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.security.ui.basicauth.BasicProcessingFilter;
+import org.springframework.security.ui.basicauth.BasicProcessingFilterEntryPoint;
+import org.w3c.dom.Element;
+
+/**
+ * Creates a {@link BasicProcessingFilter} and {@link BasicProcessingFilterEntryPoint} and
+ * registers them in the application context.
+ *
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class BasicAuthenticationBeanDefinitionParser implements BeanDefinitionParser {
+    public static final String DEFAULT_BASIC_AUTH_FILTER_ID = "_basicAuthenticationFilter";
+    public static final String DEFAULT_BASIC_AUTH_ENTRY_POINT_ID = "_basicAuthenticationEntryPoint";
+
+
+    public BeanDefinition parse(Element elt, ParserContext parserContext) {
+        BeanDefinitionBuilder filterBuilder =
+                BeanDefinitionBuilder.rootBeanDefinition(BasicProcessingFilter.class);
+        RootBeanDefinition entryPoint = new RootBeanDefinition(BasicProcessingFilterEntryPoint.class);
+
+        String realm = elt.getAttribute("realm");
+
+        entryPoint.getPropertyValues().addPropertyValue("realmName", realm);
+
+        filterBuilder.addPropertyValue("authenticationEntryPoint", entryPoint);
+        // Detect auth manager
+        filterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
+
+        parserContext.getRegistry().registerBeanDefinition(DEFAULT_BASIC_AUTH_FILTER_ID,
+                filterBuilder.getBeanDefinition());
+        parserContext.getRegistry().registerBeanDefinition(DEFAULT_BASIC_AUTH_ENTRY_POINT_ID, entryPoint);
+
+        return null;
+    }
+}

+ 96 - 0
core/src/main/java/org/springframework/security/config/FormLoginBeanDefinitionParser.java

@@ -0,0 +1,96 @@
+package org.springframework.security.config;
+
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.RootBeanDefinition;
+import org.springframework.beans.factory.xml.BeanDefinitionParser;
+import org.springframework.beans.factory.xml.ParserContext;
+import org.springframework.beans.factory.config.BeanDefinition;
+import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
+import org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint;
+import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
+import org.springframework.util.StringUtils;
+import org.w3c.dom.Element;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class FormLoginBeanDefinitionParser implements BeanDefinitionParser {
+    protected final Log logger = LogFactory.getLog(getClass());
+
+    public static final String DEFAULT_FORM_LOGIN_FILTER_ID = "_formLoginFilter";
+    public static final String DEFAULT_FORM_LOGIN_ENTRY_POINT_ID = "_formLoginEntryPoint";
+    
+    private static final String LOGIN_URL_ATTRIBUTE = "loginUrl";
+    private static final String LOGIN_PAGE_ATTRIBUTE = "loginPage";
+
+    private static final String FORM_LOGIN_TARGET_URL_ATTRIBUTE = "defaultTargetUrl";
+    private static final String DEFAULT_FORM_LOGIN_TARGET_URL = "/index";
+
+    private static final String FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE = "defaultTargetUrl";
+    // TODO: Change AbstractProcessingFilter to not need a failure URL and just write a failure message
+    // to the response if one isn't set.
+    private static final String DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL = "/loginError";
+
+
+    public BeanDefinition parse(Element elt, ParserContext parserContext) {
+        BeanDefinition filterBean = createFilterBean(elt);
+
+        BeanDefinitionBuilder entryPointBuilder =
+                BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class);
+
+
+        String loginPage = elt.getAttribute(LOGIN_PAGE_ATTRIBUTE);
+
+        // If no login page has been defined, add in the default page generator.
+        if (!StringUtils.hasText(loginPage)) {
+            logger.info("No login page configured in form-login element. The default internal one will be used. Use" +
+                    "the 'loginPage' attribute to specify the URL of the login page.");
+            loginPage = DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL;
+            RootBeanDefinition loginPageFilter = new RootBeanDefinition(DefaultLoginPageGeneratingFilter.class);
+            loginPageFilter.getConstructorArgumentValues().addGenericArgumentValue(filterBean);
+            parserContext.getRegistry().registerBeanDefinition("_springSecurityLoginPageFilter", loginPageFilter);
+        }
+
+        entryPointBuilder.addPropertyValue("loginFormUrl", loginPage);
+
+        parserContext.getRegistry().registerBeanDefinition(DEFAULT_FORM_LOGIN_FILTER_ID, filterBean);
+        parserContext.getRegistry().registerBeanDefinition(DEFAULT_FORM_LOGIN_ENTRY_POINT_ID,
+                entryPointBuilder.getBeanDefinition());
+
+        return null;
+    }
+
+    private BeanDefinition createFilterBean(Element elt) {
+        BeanDefinitionBuilder filterBuilder =
+                BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilter.class);
+
+        String loginUrl = elt.getAttribute(LOGIN_URL_ATTRIBUTE);
+
+        if (StringUtils.hasText(loginUrl)) {
+            filterBuilder.addPropertyValue("filterProcessesUrl", loginUrl);
+        }
+
+        String defaultTargetUrl = elt.getAttribute(FORM_LOGIN_TARGET_URL_ATTRIBUTE);
+
+        if (!StringUtils.hasText(defaultTargetUrl)) {
+            defaultTargetUrl = DEFAULT_FORM_LOGIN_TARGET_URL;
+        }
+
+        filterBuilder.addPropertyValue("defaultTargetUrl", defaultTargetUrl);
+
+        String authenticationFailureUrl = elt.getAttribute(FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE);
+
+        if (!StringUtils.hasText(authenticationFailureUrl)) {
+            authenticationFailureUrl = DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL;
+        }
+
+        filterBuilder.addPropertyValue("authenticationFailureUrl", authenticationFailureUrl);
+        // Set autowire to pick up the authentication manager.
+        filterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
+
+        return filterBuilder.getBeanDefinition();
+    }
+}

+ 54 - 95
core/src/main/java/org/springframework/security/config/HttpSecurityBeanDefinitionParser.java

@@ -1,34 +1,32 @@
 package org.springframework.security.config;
 
-import org.springframework.beans.factory.xml.ParserContext;
-import org.springframework.beans.factory.xml.BeanDefinitionParser;
-import org.springframework.beans.factory.support.RootBeanDefinition;
+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.config.BeanDefinition;
-import org.springframework.util.xml.DomUtils;
-import org.springframework.util.Assert;
-import org.springframework.util.StringUtils;
-import org.springframework.security.util.FilterChainProxy;
-import org.springframework.security.intercept.web.PathBasedFilterInvocationDefinitionMap;
-import org.springframework.security.intercept.web.FilterSecurityInterceptor;
-import org.springframework.security.intercept.web.FilterInvocationDefinitionMap;
-import org.springframework.security.ConfigAttributeEditor;
+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.ui.ExceptionTranslationFilter;
-import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
-import org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint;
+import org.springframework.security.ConfigAttributeEditor;
 import org.springframework.security.context.HttpSessionContextIntegrationFilter;
+import org.springframework.security.intercept.web.*;
+import org.springframework.security.ui.ExceptionTranslationFilter;
+import org.springframework.security.util.FilterChainProxy;
+import org.springframework.security.util.RegexUrlPathMatcher;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+import org.springframework.util.xml.DomUtils;
 import org.w3c.dom.Element;
 
-import java.util.List;
+import javax.servlet.Filter;
 import java.util.Iterator;
+import java.util.List;
 
 /**
  * Sets up HTTP security: filter stack and protected URLs.
  *
  *
- * @author luke
+ * @author Luke Taylor
  * @version $Id$
  */
 public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
@@ -39,39 +37,23 @@ 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_FORM_LOGIN_FILTER_ID = "_formLoginFilter";
-    public static final String DEFAULT_FORM_LOGIN_ENTRY_POINT_ID = "_formLoginEntryPoint";
 
     public static final String LOGOUT_ELEMENT = "logout";
     public static final String FORM_LOGIN_ELEMENT = "form-login";
+    public static final String BASIC_AUTH_ELEMENT = "http-basic";    
 
-    private static final String PATH_ATTRIBUTE = "path";
-    private static final String FILTERS_ATTRIBUTE = "filters";
-    private static final String ACCESS_CONFIG_ATTRIBUTE = "access";
-
-    private static final String LOGIN_URL_ATTRIBUTE = "loginUrl";
+    static final String PATH_PATTERN_ATTRIBUTE = "pattern";
+    static final String PATTERN_TYPE_ATTRIBUTE = "pathType";
+    static final String PATTERN_TYPE_REGEX = "regex";
 
-    private static final String FORM_LOGIN_TARGET_URL_ATTRIBUTE = "defaultTargetUrl";
-    private static final String DEFAULT_FORM_LOGIN_TARGET_URL = "/index";
+    static final String FILTERS_ATTRIBUTE = "filters";
+    static final String NO_FILTERS_VALUE = "none";
+    static final Filter[] EMPTY_FILTER_CHAIN = new Filter[0];
 
-    private static final String FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE = "defaultTargetUrl";
-    // TODO: Change AbstractProcessingFilter to not need a failure URL and just write a failure message
-    // to the response if one isn't set.
-    private static final String DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL = "/loginError";
+    private static final String ACCESS_CONFIG_ATTRIBUTE = "access";
 
     public BeanDefinition parse(Element element, ParserContext parserContext) {
-        // Create HttpSCIF, FilterInvocationInterceptor, ExceptionTranslationFilter
-
-        // Find other filter beans.
-
-        // Create appropriate bean list for config attributes to create FIDS
-
-        // Add any secure URLs with specific filter chains to FIDS as separate ConfigAttributes
-
-        // Add secure URLS with roles to objectDefinitionSource for FilterSecurityInterceptor
-
         RootBeanDefinition filterChainProxy = new RootBeanDefinition(FilterChainProxy.class);
-
         RootBeanDefinition httpSCIF = new RootBeanDefinition(HttpSessionContextIntegrationFilter.class);
 
         //TODO: Set session creation parameters based on session-creation attribute
@@ -79,22 +61,28 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
         BeanDefinitionBuilder filterSecurityInterceptorBuilder
                 = BeanDefinitionBuilder.rootBeanDefinition(FilterSecurityInterceptor.class);
 
-
         BeanDefinitionBuilder exceptionTranslationFilterBuilder
                 = BeanDefinitionBuilder.rootBeanDefinition(ExceptionTranslationFilter.class);
 
         // Autowire for entry point (for now)
+        // TODO: Examine entry point beans in post processing and pick the correct one
+        // i.e. form login or cas if defined, then any other non-basic, non-digest, then  basic or digest
         exceptionTranslationFilterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
 
         // TODO: Get path type attribute and determine FilDefInvS class
-        PathBasedFilterInvocationDefinitionMap filterChainInvocationDefSource
-                = new PathBasedFilterInvocationDefinitionMap();
 
-        filterChainProxy.getPropertyValues().addPropertyValue("filterInvocationDefinitionSource",
-                filterChainInvocationDefSource);
+        FilterChainMap filterChainMap =  new FilterChainMap();
+
+        String patternType = element.getAttribute(PATTERN_TYPE_ATTRIBUTE);
+
+        FilterInvocationDefinitionMap interceptorFilterInvDefSource = new PathBasedFilterInvocationDefinitionMap();
+
+        if (patternType.equals(PATTERN_TYPE_REGEX)) {
+            filterChainMap.setUrlPathMatcher(new RegexUrlPathMatcher());
+            interceptorFilterInvDefSource = new RegExpBasedFilterInvocationDefinitionMap();
+        }
 
-        PathBasedFilterInvocationDefinitionMap interceptorFilterInvDefSource
-                = new PathBasedFilterInvocationDefinitionMap();
+        filterChainProxy.getPropertyValues().addPropertyValue("filterChainMap", filterChainMap);
 
         filterSecurityInterceptorBuilder.addPropertyValue("objectDefinitionSource", interceptorFilterInvDefSource);
 
@@ -102,7 +90,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
         filterSecurityInterceptorBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
 
         parseInterceptUrls(DomUtils.getChildElementsByTagName(element, "intercept-url"),
-                filterChainInvocationDefSource, interceptorFilterInvDefSource);
+                filterChainMap, interceptorFilterInvDefSource);
         // TODO: if empty, set a default set a default /**, omitting login url
 
         BeanDefinitionRegistry registry = parserContext.getRegistry();
@@ -110,51 +98,20 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
         Element logoutElt = DomUtils.getChildElementByTagName(element, LOGOUT_ELEMENT);
 
         if (logoutElt != null) {
-            BeanDefinition logoutFilter = new LogoutBeanDefinitionParser().parse(logoutElt, parserContext);
+            new LogoutBeanDefinitionParser().parse(logoutElt, parserContext);
         }
 
         Element formLoginElt = DomUtils.getChildElementByTagName(element, FORM_LOGIN_ELEMENT);
 
         if (formLoginElt != null) {
-            BeanDefinitionBuilder formLoginFilterBuilder =
-                    BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilter.class);
-            BeanDefinitionBuilder formLoginEntryPointBuilder =
-                    BeanDefinitionBuilder.rootBeanDefinition(AuthenticationProcessingFilterEntryPoint.class);
-
-            // Temporary login value
-            formLoginEntryPointBuilder.addPropertyValue("loginFormUrl", "/login");
-
-
-            String loginUrl = formLoginElt.getAttribute(LOGIN_URL_ATTRIBUTE);
-
-            if (StringUtils.hasText(loginUrl)) {
-                formLoginFilterBuilder.addPropertyValue("filterProcessesUrl", loginUrl);
-            }
-
-            String defaultTargetUrl = formLoginElt.getAttribute(FORM_LOGIN_TARGET_URL_ATTRIBUTE);
-
-            if (!StringUtils.hasText(defaultTargetUrl)) {
-                defaultTargetUrl = DEFAULT_FORM_LOGIN_TARGET_URL;
-            }
-
-            formLoginFilterBuilder.addPropertyValue("defaultTargetUrl", defaultTargetUrl);
-
-            String authenticationFailureUrl = formLoginElt.getAttribute(FORM_LOGIN_AUTH_FAILURE_URL_ATTRIBUTE);
-
-            if (!StringUtils.hasText(authenticationFailureUrl)) {
-                authenticationFailureUrl = DEFAULT_FORM_LOGIN_AUTH_FAILURE_URL;
-            }
-
-            formLoginFilterBuilder.addPropertyValue("authenticationFailureUrl", authenticationFailureUrl);
-            // Set autowire to pick up the authentication manager.
-            formLoginFilterBuilder.setAutowireMode(RootBeanDefinition.AUTOWIRE_BY_TYPE);
+            new FormLoginBeanDefinitionParser().parse(formLoginElt, parserContext);
+        }
 
+        Element basicAuthElt = DomUtils.getChildElementByTagName(element, BASIC_AUTH_ELEMENT);
 
-            registry.registerBeanDefinition(DEFAULT_FORM_LOGIN_FILTER_ID,
-                    formLoginFilterBuilder.getBeanDefinition());
-            registry.registerBeanDefinition(DEFAULT_FORM_LOGIN_ENTRY_POINT_ID,
-                    formLoginEntryPointBuilder.getBeanDefinition());
-        }
+        if (basicAuthElt != null) {
+            new BasicAuthenticationBeanDefinitionParser().parse(basicAuthElt, parserContext);
+        }        
 
         registry.registerBeanDefinition(DEFAULT_FILTER_CHAIN_PROXY_ID, filterChainProxy);
         registry.registerBeanDefinition(DEFAULT_HTTP_SESSION_FILTER_ID, httpSCIF);
@@ -168,15 +125,16 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
         // app context has been created and all beans are available.
 
         registry.registerBeanDefinition("__httpConfigBeanFactoryPostProcessor",
-                new RootBeanDefinition(HttpSecurityConfigPostProcessor.class));        
+                new RootBeanDefinition(HttpSecurityConfigPostProcessor.class));
 
         return null;
     }
 
     /**
-     * Parses the intercept-url elements and populates the FilterChainProxy's FilterInvocationDefinitionSource
+     * Parses the intercept-url elements and populates the FilterChainProxy's FilterChainMap and the
+     * FilterInvocationDefinitionSource used in FilterSecurityInterceptor.
      */
-    private void parseInterceptUrls(List urlElts, FilterInvocationDefinitionMap filterChainInvocationDefSource,
+    private void parseInterceptUrls(List urlElts, FilterChainMap filterChainMap,
             FilterInvocationDefinitionMap interceptorFilterInvDefSource) {
 
         Iterator urlEltsIterator = urlElts.iterator();
@@ -186,7 +144,7 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
         while (urlEltsIterator.hasNext()) {
             Element urlElt = (Element) urlEltsIterator.next();
 
-            String path = urlElt.getAttribute(PATH_ATTRIBUTE);
+            String path = urlElt.getAttribute(PATH_PATTERN_ATTRIBUTE);
 
             Assert.hasText(path, "path attribute cannot be empty or null");
 
@@ -204,11 +162,12 @@ public class HttpSecurityBeanDefinitionParser implements BeanDefinitionParser {
             String filters = urlElt.getAttribute(FILTERS_ATTRIBUTE);
 
             if (StringUtils.hasText(filters)) {
-                attributeEditor.setAsText(filters);
-            }
-
-
+                if (!filters.equals(NO_FILTERS_VALUE)) {
+                    throw new IllegalStateException("Currently only 'none' is supported as the custom filters attribute");
+                }
 
+                filterChainMap.addSecureUrl(path, EMPTY_FILTER_CHAIN);
+            }
         }
     }
 }

+ 85 - 14
core/src/main/java/org/springframework/security/config/HttpSecurityConfigPostProcessor.java

@@ -1,15 +1,22 @@
 package org.springframework.security.config;
 
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.BeansException;
+import org.springframework.beans.factory.config.BeanDefinition;
 import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
-import org.springframework.beans.BeansException;
-import org.springframework.security.util.FilterChainProxy;
-import org.springframework.security.context.HttpSessionContextIntegrationFilter;
+import org.springframework.core.OrderComparator;
+import org.springframework.core.Ordered;
 import org.springframework.security.AuthenticationManager;
+import org.springframework.security.context.HttpSessionContextIntegrationFilter;
+import org.springframework.security.intercept.web.FilterChainMap;
+import org.springframework.security.ui.AuthenticationEntryPoint;
+import org.springframework.security.util.FilterChainProxy;
 import org.springframework.util.Assert;
 
 import javax.servlet.Filter;
-import java.util.Map;
+import java.util.*;
 
 /**
  * Responsible for tying up the HTTP security configuration - building ordered filter stack and linking up
@@ -18,36 +25,96 @@ import java.util.Map;
  * @author Luke Taylor
  * @version $Id$
  */
-public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor {
-    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
-        FilterChainProxy filterChainProxy =
-                (FilterChainProxy) beanFactory.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
+public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor, Ordered {
+    private Log logger = LogFactory.getLog(getClass());
 
+    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
         HttpSessionContextIntegrationFilter httpSCIF = (HttpSessionContextIntegrationFilter)
                 beanFactory.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_HTTP_SESSION_FILTER_ID);
-
         AuthenticationManager authManager =
                 (AuthenticationManager) getBeanOfType(AuthenticationManager.class, beanFactory);
 
+        configureAuthenticationEntryPoint(beanFactory);
 
-        //
-        Map filters = beanFactory.getBeansOfType(Filter.class);
+        configureFilterChain(beanFactory);
+    }
 
+    /**
+     * Selects the entry point that should be used in ExceptionTranslationFilter. Strategy is
+     *
+     * <ol>
+     * <li>If only one use that.</li>
+     * <li>If more than one, check the default interactive login Ids in order of preference</li>
+     * <li>throw an exception (for now). TODO: Examine additional beans and types and make decision</li>
+     * </ol>
+     *
+     *
+     * @param beanFactory
+     */
+    private void configureAuthenticationEntryPoint(ConfigurableListableBeanFactory beanFactory) {
+        logger.info("Selecting AuthenticationEntryPoint for use in ExceptionTranslationFilter");
+
+        BeanDefinition etf =
+                beanFactory.getBeanDefinition(HttpSecurityBeanDefinitionParser.DEFAULT_EXCEPTION_TRANSLATION_FILTER_ID);
+        Map entryPointMap = beanFactory.getBeansOfType(AuthenticationEntryPoint.class);
+        List entryPoints = new ArrayList(entryPointMap.values());
+
+        Assert.isTrue(entryPoints.size() > 0, "No AuthenticationEntryPoint instances defined");
+
+        AuthenticationEntryPoint mainEntryPoint = (AuthenticationEntryPoint)
+                entryPointMap.get(FormLoginBeanDefinitionParser.DEFAULT_FORM_LOGIN_ENTRY_POINT_ID);
+
+        if (mainEntryPoint == null) {
+            throw new SecurityConfigurationException("Failed to resolve authentication entry point");
+        }
+
+        logger.info("Main AuthenticationEntryPoint set to " + mainEntryPoint);
+
+        etf.getPropertyValues().addPropertyValue("authenticationEntryPoint", mainEntryPoint);
+    }
 
+    private void configureFilterChain(ConfigurableListableBeanFactory beanFactory) {
+        FilterChainProxy filterChainProxy =
+                (FilterChainProxy) beanFactory.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
+        // Set the default match
+        List defaultFilterChain = orderFilters(beanFactory);
 
+        FilterChainMap filterMap = filterChainProxy.getFilterChainMap();
 
+        String allUrlsMatch = filterMap.getMatcher().getUniversalMatchPattern();
 
+        filterMap.addSecureUrl(allUrlsMatch, (Filter[]) defaultFilterChain.toArray(new Filter[0]));
+    }
+       
+    private List orderFilters(ConfigurableListableBeanFactory beanFactory) {
+        Map filters = beanFactory.getBeansOfType(Filter.class);
 
+        Assert.notEmpty(filters, "No filters found in app context!");
 
+        Iterator ids = filters.keySet().iterator();
 
-    }
+        List orderedFilters = new ArrayList();
 
-    private void configureFilterChain(ConfigurableListableBeanFactory beanFactory) {
+        while (ids.hasNext()) {
+            String id = (String) ids.next();
+            Filter filter = (Filter) filters.get(id);
 
+            if (filter instanceof FilterChainProxy) {
+                continue;
+            }
 
-    }
+            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");
+            }
+
+            orderedFilters.add(filter);
+        }
 
+        Collections.sort(orderedFilters, new OrderComparator());
 
+        return orderedFilters;
+    }
 
     private Object getBeanOfType(Class clazz, ConfigurableListableBeanFactory beanFactory) {
         Map beans = beanFactory.getBeansOfType(clazz);
@@ -56,4 +123,8 @@ public class HttpSecurityConfigPostProcessor implements BeanFactoryPostProcessor
 
         return beans.values().toArray()[0];
     }
+
+    public int getOrder() {
+        return 0;
+    }
 }

+ 105 - 0
core/src/main/java/org/springframework/security/ui/webapp/DefaultLoginPageGeneratingFilter.java

@@ -0,0 +1,105 @@
+package org.springframework.security.ui.webapp;
+
+import org.springframework.security.AuthenticationException;
+import org.springframework.security.ui.AbstractProcessingFilter;
+import org.springframework.security.ui.FilterChainOrderUtils;
+import org.springframework.security.ui.SpringSecurityFilter;
+import org.springframework.security.ui.rememberme.TokenBasedRememberMeServices;
+import org.springframework.util.StringUtils;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import java.io.IOException;
+
+/**
+ * For internal use with namespace configuration in the case where a user doesn't configure a login page.
+ * The configuration code will insert this filter in the chain instead.
+ *
+ * Will only work if a redirect is used to the login page.
+ *
+ * @author Luke Taylor
+ * @version $Id$
+ */
+public class DefaultLoginPageGeneratingFilter extends SpringSecurityFilter {
+    public static final String DEFAULT_LOGIN_PAGE_URL = "/login";
+    private String authenticationUrl;
+    private String usernameParameter;
+    private String passwordParameter;
+    private String rememberMeParameter;
+
+    public DefaultLoginPageGeneratingFilter(AuthenticationProcessingFilter authFilter) {
+        authenticationUrl = authFilter.getDefaultFilterProcessesUrl();
+        usernameParameter = authFilter.getUsernameParameter();
+        passwordParameter = authFilter.getPasswordParameter();
+
+        if (authFilter.getRememberMeServices() instanceof TokenBasedRememberMeServices) {
+            rememberMeParameter = ((TokenBasedRememberMeServices)authFilter.getRememberMeServices()).getParameter();
+        }
+    }
+
+    protected void doFilterHttp(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
+        if (isLoginUrlRequest(request)) {
+            response.getOutputStream().print(generateLoginPageHtml(request));
+
+            return;
+        }
+
+        chain.doFilter(request, response);
+    }
+
+    private String generateLoginPageHtml(HttpServletRequest request) {
+        boolean loginError = StringUtils.hasText(request.getParameter("login_error"));
+        String errorMsg = "none";
+        String lastUser = "";
+
+        if (loginError) {
+            HttpSession session = request.getSession(false);
+
+            if(session != null) {
+                 errorMsg = ((AuthenticationException)
+                        session.getAttribute(AbstractProcessingFilter.SPRING_SECURITY_LAST_EXCEPTION_KEY)).getMessage();
+            }
+        }
+
+        return "<html><head><title>Login Page</title></head><body>\n" +
+                (loginError ? ("<font color='red'>Your login attempt was not successful, try again.<br/><br/>Reason: " +
+                        errorMsg + "</font>") : "") +
+                " <form action='" + request.getContextPath() + authenticationUrl + "' method='POST'>\n" +
+                "   <table>\n" +
+                "     <tr><td>User:</td><td><input type='text' name='" + usernameParameter + "'  value='" + lastUser +
+                "'></td></tr>\n" +
+                "     <tr><td>Password:</td><td><input type='password' name='"+ passwordParameter +"'></td></tr>\n" +
+
+                (rememberMeParameter == null ? "" :
+                "     <tr><td><input type='checkbox' name='"+ rememberMeParameter +
+                        "'></td><td>Remember me on this computer.</td></tr>\n"
+                ) +
+                "     <tr><td colspan='2'><input name=\"submit\" type=\"submit\"></td></tr>\n" +
+                "     <tr><td colspan='2'><input name=\"reset\" type=\"reset\"></td></tr>\n" +
+                "   </table>\n" +
+                " </form></body></html>";
+    }
+
+    public int getOrder() {
+        return FilterChainOrderUtils.LOGIN_PAGE_FILTER_ORDER;
+    }
+
+	private boolean isLoginUrlRequest(HttpServletRequest request) {
+		String uri = request.getRequestURI();
+		int pathParamIndex = uri.indexOf(';');
+
+		if (pathParamIndex > 0) {
+			// strip everything after the first semi-colon
+			uri = uri.substring(0, pathParamIndex);
+		}
+
+		if ("".equals(request.getContextPath())) {
+			return uri.endsWith(DEFAULT_LOGIN_PAGE_URL);
+		}
+
+		return uri.endsWith(request.getContextPath() + DEFAULT_LOGIN_PAGE_URL);
+	}
+}

+ 51 - 2
core/src/test/java/org/springframework/security/config/HttpSecurityBeanDefinitionParserTests.java

@@ -1,8 +1,21 @@
 package org.springframework.security.config;
 
-import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.junit.AfterClass;
+import static org.junit.Assert.assertTrue;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.springframework.context.support.ClassPathXmlApplicationContext;
+import org.springframework.security.context.HttpSessionContextIntegrationFilter;
+import org.springframework.security.intercept.web.FilterChainMap;
+import org.springframework.security.intercept.web.FilterSecurityInterceptor;
+import org.springframework.security.ui.ExceptionTranslationFilter;
+import org.springframework.security.ui.basicauth.BasicProcessingFilter;
+import org.springframework.security.ui.logout.LogoutFilter;
+import org.springframework.security.ui.webapp.AuthenticationProcessingFilter;
+import org.springframework.security.ui.webapp.DefaultLoginPageGeneratingFilter;
+import org.springframework.security.util.FilterChainProxy;
+
+import javax.servlet.Filter;
 
 /**
  * @author Luke Taylor
@@ -16,8 +29,44 @@ public class HttpSecurityBeanDefinitionParserTests {
         appContext = new ClassPathXmlApplicationContext("org/springframework/security/config/http-security.xml");
     }
 
+    @AfterClass
+    public static void closeAppContext() {
+        if (appContext != null) {
+            appContext.close();
+        }
+    }
+
     @Test
-    public void testContextContainsExpectedBeansAndData() {
+    public void filterChainProxyShouldReturnEmptyFilterListForUnprotectedUrl() {
+        FilterChainProxy filterChainProxy =
+                (FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
+
+        FilterChainMap filterChainMap = filterChainProxy.getFilterChainMap();
+
+        Filter[] filters = filterChainMap.getFilters("/unprotected");
+
+        assertTrue(filters.length == 0);
     }
 
+    @Test
+    public void filterChainProxyShouldReturnCorrectFilterListForProtectedUrl() {
+        FilterChainProxy filterChainProxy =
+                (FilterChainProxy) appContext.getBean(HttpSecurityBeanDefinitionParser.DEFAULT_FILTER_CHAIN_PROXY_ID);
+
+        FilterChainMap filterChainMap = filterChainProxy.getFilterChainMap();
+
+        Filter[] filters = filterChainMap.getFilters("/someurl");
+
+
+        
+        assertTrue("Expected 7 filters in chain", filters.length == 7);
+
+        assertTrue(filters[0] instanceof HttpSessionContextIntegrationFilter);
+        assertTrue(filters[1] instanceof LogoutFilter);
+        assertTrue(filters[2] instanceof AuthenticationProcessingFilter);
+        assertTrue(filters[3] instanceof DefaultLoginPageGeneratingFilter);
+        assertTrue(filters[4] instanceof BasicProcessingFilter);
+        assertTrue(filters[5] instanceof ExceptionTranslationFilter);
+        assertTrue(filters[6] instanceof FilterSecurityInterceptor);
+    }
 }

+ 11 - 9
core/src/test/resources/org/springframework/security/config/http-security.xml

@@ -6,20 +6,22 @@
     xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.xsd">
 
-    <security:autoconfig />    
+    <security:autoconfig />
 
     <security:http createSession="ifRequired" pathType="ant" lowerCaseComparisons="true">
-        <security:intercept-url path="/unprotected" filters="none"/>
-        <security:intercept-url path="/somepath" filters="filter1,filter2,filter3" access="ROLE_SPECIAL,ROLE_USER" />
-        <security:intercept-url path="/**" access="ROLE_USER" />
+        <security:intercept-url pattern="/unprotected" filters="none"/>
+        <security:intercept-url pattern="/somepath" access="ROLE_SPECIAL,ROLE_USER" />
+        <security:intercept-url pattern="/**" access="ROLE_USER" />
+
+        <!-- Default logout configuration -->
+        <security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />        
 
         <!-- Default form login configuration. Will create filter and entry point -->
         <security:form-login loginUrl="/j_spring_security_check"  />
 
-        <!-- Default logout configuration -->
-        <security:logout logoutUrl="/j_spring_security_logout" logoutSuccessUrl="/" invalidateSession="true" />
-
-    </security:http>    
+        <!-- Default basic auth configuration. Will create filter and entry point -->
+        <security:http-basic realm="NamespaceTestRealm"  />
+    </security:http>
 
 
     <security:authentication-provider>
@@ -27,6 +29,6 @@ http://www.springframework.org/schema/security http://www.springframework.org/sc
             <security:user name="bob" password="bobspassword" authorities="ROLE_A,ROLE_B" />
             <security:user name="bill" password="billspassword" authorities="ROLE_A,ROLE_B,AUTH_OTHER" />
         </security:user-service>
-    </security:authentication-provider>    
+    </security:authentication-provider>
 
 </beans>