Browse Source

Make SecurityEnforcementFilter support pluggable authentication entry points. Enhance BASIC authentication so it's a viable alternative to form-based authentication for user agents like IE and Netscape.

Ben Alex 21 năm trước cách đây
mục cha
commit
6815e693a7

+ 56 - 0
core/src/main/java/org/acegisecurity/intercept/web/AuthenticationEntryPoint.java

@@ -0,0 +1,56 @@
+/* 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.intercept.web;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+
+
+/**
+ * Used by {@link SecurityEnforcementFilter} to commence an authentication
+ * scheme.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface AuthenticationEntryPoint {
+    //~ Methods ================================================================
+
+    /**
+     * Commences an authentication scheme.
+     * 
+     * <P>
+     * <code>SecurityEnforcementFilter</code> will populate the
+     * <code>HttpSession</code> attribute named
+     * <code>AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY</code>
+     * with the requested target URL before calling this method.
+     * </p>
+     * 
+     * <P>
+     * Implementations should modify the headers on the
+     * <code>ServletResponse</code> to as necessary to commence the
+     * authentication process.
+     * </p>
+     *
+     * @param request that resulted in an <code>AuthenticationException</code>
+     * @param response so that the user agent can begin authentication
+     */
+    public void commence(ServletRequest request, ServletResponse response)
+        throws IOException, ServletException;
+}

+ 19 - 23
core/src/main/java/org/acegisecurity/intercept/web/SecurityEnforcementFilter.java

@@ -59,8 +59,7 @@ import javax.servlet.http.HttpServletResponse;
  * </p>
  * </p>
  * 
  * 
  * <p>
  * <p>
- * To use this filter, it is necessary to specify the following filter
- * initialization parameters:
+ * To use this filter, it is necessary to specify the following properties:
  * </p>
  * </p>
  * 
  * 
  * <ul>
  * <ul>
@@ -70,8 +69,9 @@ import javax.servlet.http.HttpServletResponse;
  * to.
  * to.
  * </li>
  * </li>
  * <li>
  * <li>
- * <code>loginFormUrl</code> indicates the URL that should be used for
- * redirection if an <code>AuthenticationException</code> is detected.
+ * <code>authenticationEntryPoint</code> indicates the handler that should
+ * commence the authentication process if an
+ * <code>AuthenticationException</code> is detected.
  * </li>
  * </li>
  * </ul>
  * </ul>
  * 
  * 
@@ -92,16 +92,20 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
 
 
     //~ Instance fields ========================================================
     //~ Instance fields ========================================================
 
 
-    protected FilterSecurityInterceptor filterSecurityInterceptor;
-
-    /**
-     * The URL that should be used for redirection if an
-     * <code>AuthenticationException</code> is detected.
-     */
-    protected String loginFormUrl;
+    private AuthenticationEntryPoint authenticationEntryPoint;
+    private FilterSecurityInterceptor filterSecurityInterceptor;
 
 
     //~ Methods ================================================================
     //~ Methods ================================================================
 
 
+    public void setAuthenticationEntryPoint(
+        AuthenticationEntryPoint authenticationEntryPoint) {
+        this.authenticationEntryPoint = authenticationEntryPoint;
+    }
+
+    public AuthenticationEntryPoint getAuthenticationEntryPoint() {
+        return authenticationEntryPoint;
+    }
+
     public void setFilterSecurityInterceptor(
     public void setFilterSecurityInterceptor(
         FilterSecurityInterceptor filterSecurityInterceptor) {
         FilterSecurityInterceptor filterSecurityInterceptor) {
         this.filterSecurityInterceptor = filterSecurityInterceptor;
         this.filterSecurityInterceptor = filterSecurityInterceptor;
@@ -111,17 +115,10 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
         return filterSecurityInterceptor;
         return filterSecurityInterceptor;
     }
     }
 
 
-    public void setLoginFormUrl(String loginFormUrl) {
-        this.loginFormUrl = loginFormUrl;
-    }
-
-    public String getLoginFormUrl() {
-        return loginFormUrl;
-    }
-
     public void afterPropertiesSet() throws Exception {
     public void afterPropertiesSet() throws Exception {
-        if ((loginFormUrl == null) || "".equals(loginFormUrl)) {
-            throw new IllegalArgumentException("loginFormUrl must be specified");
+        if (authenticationEntryPoint == null) {
+            throw new IllegalArgumentException(
+                "authenticationEntryPoint must be specified");
         }
         }
 
 
         if (filterSecurityInterceptor == null) {
         if (filterSecurityInterceptor == null) {
@@ -161,8 +158,7 @@ public class SecurityEnforcementFilter implements Filter, InitializingBean {
 
 
             ((HttpServletRequest) request).getSession().setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
             ((HttpServletRequest) request).getSession().setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
                 fi.getRequestUrl());
                 fi.getRequestUrl());
-            ((HttpServletResponse) response).sendRedirect(((HttpServletRequest) request)
-                .getContextPath() + loginFormUrl);
+            authenticationEntryPoint.commence(request, response);
         } catch (AccessDeniedException accessDenied) {
         } catch (AccessDeniedException accessDenied) {
             if (logger.isDebugEnabled()) {
             if (logger.isDebugEnabled()) {
                 logger.debug(
                 logger.debug(

+ 23 - 8
core/src/main/java/org/acegisecurity/ui/basicauth/BasicProcessingFilter.java

@@ -18,6 +18,7 @@ package net.sf.acegisecurity.ui.basicauth;
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.AuthenticationException;
 import net.sf.acegisecurity.AuthenticationException;
 import net.sf.acegisecurity.AuthenticationManager;
 import net.sf.acegisecurity.AuthenticationManager;
+import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
 import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
 import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
 
 
@@ -63,9 +64,9 @@ import javax.servlet.http.HttpServletResponse;
  * </p>
  * </p>
  * 
  * 
  * <p>
  * <p>
- * Requests containing BASIC authentication headers are generally created by
- * remoting protocol libraries.  This filter is intended to process requests
- * made by such libraries.
+ * This filter can be used to provide BASIC authentication services to both
+ * remoting protocol clients (such as Hessian and SOAP) as well as standard
+ * user agents (such as Internet Explorer and Netscape).
  * </p>
  * </p>
  * 
  * 
  * <P>
  * <P>
@@ -75,10 +76,9 @@ import javax.servlet.http.HttpServletResponse;
  * </p>
  * </p>
  * 
  * 
  * <p>
  * <p>
- * If authentication fails, a <code>HttpServletResponse.SC_FORBIDDEN</code>
- * (403 error) response is sent. This is consistent with RFC 1945, Section 11,
- * which states, "<I>If the server does not wish to accept the credentials
- * sent with a request, it should return a 403 (forbidden) response.</I>".
+ * If authentication fails, an {@link AuthenticationEntryPoint} implementation
+ * is called. Usually this should be {@link BasicProcessingFilterEntryPoint},
+ * which will prompt the user to authenticate again via BASIC authentication.
  * </p>
  * </p>
  * 
  * 
  * <P>
  * <P>
@@ -97,10 +97,20 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
 
 
     //~ Instance fields ========================================================
     //~ Instance fields ========================================================
 
 
+    private AuthenticationEntryPoint authenticationEntryPoint;
     private AuthenticationManager authenticationManager;
     private AuthenticationManager authenticationManager;
 
 
     //~ Methods ================================================================
     //~ Methods ================================================================
 
 
+    public void setAuthenticationEntryPoint(
+        AuthenticationEntryPoint authenticationEntryPoint) {
+        this.authenticationEntryPoint = authenticationEntryPoint;
+    }
+
+    public AuthenticationEntryPoint getAuthenticationEntryPoint() {
+        return authenticationEntryPoint;
+    }
+
     public void setAuthenticationManager(
     public void setAuthenticationManager(
         AuthenticationManager authenticationManager) {
         AuthenticationManager authenticationManager) {
         this.authenticationManager = authenticationManager;
         this.authenticationManager = authenticationManager;
@@ -115,6 +125,11 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
             throw new IllegalArgumentException(
             throw new IllegalArgumentException(
                 "An AuthenticationManager is required");
                 "An AuthenticationManager is required");
         }
         }
+
+        if (this.authenticationEntryPoint == null) {
+            throw new IllegalArgumentException(
+                "An AuthenticationEntryPoint is required");
+        }
     }
     }
 
 
     public void destroy() {}
     public void destroy() {}
@@ -166,7 +181,7 @@ public class BasicProcessingFilter implements Filter, InitializingBean {
                         + " failed: " + failed.toString());
                         + " failed: " + failed.toString());
                 }
                 }
 
 
-                ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN); // 403
+                authenticationEntryPoint.commence(request, response);
 
 
                 return;
                 return;
             }
             }

+ 75 - 0
core/src/main/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterEntryPoint.java

@@ -0,0 +1,75 @@
+/* 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.ui.basicauth;
+
+import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
+
+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.HttpServletResponse;
+
+
+/**
+ * Used by the <code>SecurityEnforcementFilter</code> to commence
+ * authentication via the {@link BasicProcessingFilter}.
+ * 
+ * <P>
+ * Once a user agent is authenticated using BASIC authentication, logout
+ * requires that the browser be closed or an unauthorized (401) header be
+ * sent. The simplest way of achieving the latter is to call the {@link
+ * #commence(ServletRequest, ServletResponse)} method below. This will
+ * indicate to the browser its credentials are no longer authorized, causing
+ * it to prompt the user to login again.
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BasicProcessingFilterEntryPoint implements AuthenticationEntryPoint,
+    InitializingBean {
+    //~ Instance fields ========================================================
+
+    private String realmName;
+
+    //~ Methods ================================================================
+
+    public void setRealmName(String realmName) {
+        this.realmName = realmName;
+    }
+
+    public String getRealmName() {
+        return realmName;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if ((realmName == null) || "".equals(realmName)) {
+            throw new IllegalArgumentException("realmName must be specified");
+        }
+    }
+
+    public void commence(ServletRequest request, ServletResponse response)
+        throws IOException, ServletException {
+        HttpServletResponse httpResponse = (HttpServletResponse) response;
+        httpResponse.addHeader("WWW-Authenticate",
+            "Basic realm=\"" + realmName + "\"");
+        httpResponse.sendError(401);
+    }
+}

+ 69 - 0
core/src/main/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPoint.java

@@ -0,0 +1,69 @@
+/* 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.ui.webapp;
+
+import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
+
+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;
+
+
+/**
+ * Used by the <code>SecurityEnforcementFilter</code> to commence
+ * authentication via the {@link AuthenticationProcessingFilter}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AuthenticationProcessingFilterEntryPoint
+    implements AuthenticationEntryPoint, InitializingBean {
+    //~ Instance fields ========================================================
+
+    /**
+     * The URL where the <code>AuthenticationProcessingFilter</code> login page
+     * can be found.
+     */
+    private String loginFormUrl;
+
+    //~ Methods ================================================================
+
+    public void setLoginFormUrl(String loginFormUrl) {
+        this.loginFormUrl = loginFormUrl;
+    }
+
+    public String getLoginFormUrl() {
+        return loginFormUrl;
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if ((loginFormUrl == null) || "".equals(loginFormUrl)) {
+            throw new IllegalArgumentException("loginFormUrl must be specified");
+        }
+    }
+
+    public void commence(ServletRequest request, ServletResponse response)
+        throws IOException, ServletException {
+        ((HttpServletResponse) response).sendRedirect(((HttpServletRequest) request)
+            .getContextPath() + loginFormUrl);
+    }
+}

+ 57 - 0
core/src/test/java/org/acegisecurity/MockAuthenticationEntryPoint.java

@@ -0,0 +1,57 @@
+/* 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;
+
+import net.sf.acegisecurity.intercept.web.AuthenticationEntryPoint;
+
+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;
+
+
+/**
+ * Performs a HTTP redirect to the constructor-indicated URL.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class MockAuthenticationEntryPoint implements AuthenticationEntryPoint {
+    //~ Instance fields ========================================================
+
+    private String url;
+
+    //~ Constructors ===========================================================
+
+    public MockAuthenticationEntryPoint(String url) {
+        this.url = url;
+    }
+
+    private MockAuthenticationEntryPoint() {
+        super();
+    }
+
+    //~ Methods ================================================================
+
+    public void commence(ServletRequest request, ServletResponse response)
+        throws IOException, ServletException {
+        ((HttpServletResponse) response).sendRedirect(((HttpServletRequest) request)
+            .getContextPath() + url);
+    }
+}

+ 14 - 1
core/src/test/java/org/acegisecurity/MockHttpServletResponse.java

@@ -18,7 +18,9 @@ package net.sf.acegisecurity;
 import java.io.IOException;
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.io.PrintWriter;
 
 
+import java.util.HashMap;
 import java.util.Locale;
 import java.util.Locale;
+import java.util.Map;
 
 
 import javax.servlet.ServletOutputStream;
 import javax.servlet.ServletOutputStream;
 import javax.servlet.http.Cookie;
 import javax.servlet.http.Cookie;
@@ -35,6 +37,7 @@ import javax.servlet.http.HttpServletResponse;
 public class MockHttpServletResponse implements HttpServletResponse {
 public class MockHttpServletResponse implements HttpServletResponse {
     //~ Instance fields ========================================================
     //~ Instance fields ========================================================
 
 
+    private Map headersMap = new HashMap();
     private String redirect;
     private String redirect;
     private int error;
     private int error;
 
 
@@ -76,6 +79,16 @@ public class MockHttpServletResponse implements HttpServletResponse {
         throw new UnsupportedOperationException("mock method not implemented");
         throw new UnsupportedOperationException("mock method not implemented");
     }
     }
 
 
+    public String getHeader(String arg0) {
+        Object result = headersMap.get(arg0);
+
+        if (result != null) {
+            return (String) headersMap.get(arg0);
+        } else {
+            return null;
+        }
+    }
+
     public void setIntHeader(String arg0, int arg1) {
     public void setIntHeader(String arg0, int arg1) {
         throw new UnsupportedOperationException("mock method not implemented");
         throw new UnsupportedOperationException("mock method not implemented");
     }
     }
@@ -117,7 +130,7 @@ public class MockHttpServletResponse implements HttpServletResponse {
     }
     }
 
 
     public void addHeader(String arg0, String arg1) {
     public void addHeader(String arg0, String arg1) {
-        throw new UnsupportedOperationException("mock method not implemented");
+        headersMap.put(arg0, arg1);
     }
     }
 
 
     public void addIntHeader(String arg0, int arg1) {
     public void addIntHeader(String arg0, int arg1) {

+ 19 - 12
core/src/test/java/org/acegisecurity/intercept/web/SecurityEnforcementFilterTests.java

@@ -19,6 +19,7 @@ import junit.framework.TestCase;
 
 
 import net.sf.acegisecurity.AccessDeniedException;
 import net.sf.acegisecurity.AccessDeniedException;
 import net.sf.acegisecurity.BadCredentialsException;
 import net.sf.acegisecurity.BadCredentialsException;
+import net.sf.acegisecurity.MockAuthenticationEntryPoint;
 import net.sf.acegisecurity.MockHttpServletRequest;
 import net.sf.acegisecurity.MockHttpServletRequest;
 import net.sf.acegisecurity.MockHttpServletResponse;
 import net.sf.acegisecurity.MockHttpServletResponse;
 import net.sf.acegisecurity.MockHttpSession;
 import net.sf.acegisecurity.MockHttpSession;
@@ -76,7 +77,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
         // Test
         // Test
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         filter.setFilterSecurityInterceptor(interceptor);
         filter.setFilterSecurityInterceptor(interceptor);
-        filter.setLoginFormUrl("/login.jsp");
+        filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
+                "/login.jsp"));
 
 
         MockHttpServletResponse response = new MockHttpServletResponse();
         MockHttpServletResponse response = new MockHttpServletResponse();
         filter.doFilter(request, response, chain);
         filter.doFilter(request, response, chain);
@@ -115,8 +117,9 @@ public class SecurityEnforcementFilterTests extends TestCase {
                 false, false));
                 false, false));
         assertTrue(filter.getFilterSecurityInterceptor() != null);
         assertTrue(filter.getFilterSecurityInterceptor() != null);
 
 
-        filter.setLoginFormUrl("/u");
-        assertEquals("/u", filter.getLoginFormUrl());
+        filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
+                "/login.jsp"));
+        assertTrue(filter.getAuthenticationEntryPoint() != null);
     }
     }
 
 
     public void testRedirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthenticationException()
     public void testRedirectedToLoginFormAndSessionShowsOriginalTargetWhenAuthenticationException()
@@ -136,7 +139,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
         // Test
         // Test
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         filter.setFilterSecurityInterceptor(interceptor);
         filter.setFilterSecurityInterceptor(interceptor);
-        filter.setLoginFormUrl("/login.jsp");
+        filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
+                "/login.jsp"));
         filter.afterPropertiesSet();
         filter.afterPropertiesSet();
 
 
         MockHttpServletResponse response = new MockHttpServletResponse();
         MockHttpServletResponse response = new MockHttpServletResponse();
@@ -146,31 +150,33 @@ public class SecurityEnforcementFilterTests extends TestCase {
             request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY));
             request.getSession().getAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY));
     }
     }
 
 
-    public void testStartupDetectsMissingFilterSecurityInterceptor()
+    public void testStartupDetectsMissingAuthenticationEntryPoint()
         throws Exception {
         throws Exception {
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
-        filter.setLoginFormUrl("/login.jsp");
+        filter.setFilterSecurityInterceptor(new MockFilterSecurityInterceptor(
+                false, false));
 
 
         try {
         try {
             filter.afterPropertiesSet();
             filter.afterPropertiesSet();
             fail("Should have thrown IllegalArgumentException");
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         } catch (IllegalArgumentException expected) {
-            assertEquals("filterSecurityInterceptor must be specified",
+            assertEquals("authenticationEntryPoint must be specified",
                 expected.getMessage());
                 expected.getMessage());
         }
         }
     }
     }
 
 
-    public void testStartupDetectsMissingLoginFormUrl()
+    public void testStartupDetectsMissingFilterSecurityInterceptor()
         throws Exception {
         throws Exception {
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
-        filter.setFilterSecurityInterceptor(new MockFilterSecurityInterceptor(
-                false, false));
+        filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
+                "/login.jsp"));
 
 
         try {
         try {
             filter.afterPropertiesSet();
             filter.afterPropertiesSet();
             fail("Should have thrown IllegalArgumentException");
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         } catch (IllegalArgumentException expected) {
-            assertEquals("loginFormUrl must be specified", expected.getMessage());
+            assertEquals("filterSecurityInterceptor must be specified",
+                expected.getMessage());
         }
         }
     }
     }
 
 
@@ -190,7 +196,8 @@ public class SecurityEnforcementFilterTests extends TestCase {
         // Test
         // Test
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         SecurityEnforcementFilter filter = new SecurityEnforcementFilter();
         filter.setFilterSecurityInterceptor(interceptor);
         filter.setFilterSecurityInterceptor(interceptor);
-        filter.setLoginFormUrl("/login.jsp");
+        filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
+                "/login.jsp"));
 
 
         MockHttpServletResponse response = new MockHttpServletResponse();
         MockHttpServletResponse response = new MockHttpServletResponse();
         filter.doFilter(request, response, chain);
         filter.doFilter(request, response, chain);

+ 82 - 0
core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterEntryPointTests.java

@@ -0,0 +1,82 @@
+/* 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.ui.basicauth;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+
+
+/**
+ * Tests {@link BasicProcessingFilterEntryPoint}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class BasicProcessingFilterEntryPointTests extends TestCase {
+    //~ Constructors ===========================================================
+
+    public BasicProcessingFilterEntryPointTests() {
+        super();
+    }
+
+    public BasicProcessingFilterEntryPointTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(BasicProcessingFilterEntryPointTests.class);
+    }
+
+    public void testDetectsMissingRealmName() throws Exception {
+        BasicProcessingFilterEntryPoint ep = new BasicProcessingFilterEntryPoint();
+
+        try {
+            ep.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("realmName must be specified", expected.getMessage());
+        }
+    }
+
+    public void testGettersSetters() {
+        BasicProcessingFilterEntryPoint ep = new BasicProcessingFilterEntryPoint();
+        ep.setRealmName("realm");
+        assertEquals("realm", ep.getRealmName());
+    }
+
+    public void testNormalOperation() throws Exception {
+        BasicProcessingFilterEntryPoint ep = new BasicProcessingFilterEntryPoint();
+        ep.setRealmName("hello");
+
+        MockHttpServletRequest request = new MockHttpServletRequest(
+                "/some_path");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        ep.afterPropertiesSet();
+        ep.commence(request, response);
+        assertEquals(401, response.getError());
+        assertEquals("Basic realm=\"hello\"",
+            response.getHeader("WWW-Authenticate"));
+    }
+}

+ 22 - 2
core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java

@@ -18,6 +18,7 @@ package net.sf.acegisecurity.ui.basicauth;
 import junit.framework.TestCase;
 import junit.framework.TestCase;
 
 
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.MockAuthenticationEntryPoint;
 import net.sf.acegisecurity.MockAuthenticationManager;
 import net.sf.acegisecurity.MockAuthenticationManager;
 import net.sf.acegisecurity.MockFilterConfig;
 import net.sf.acegisecurity.MockFilterConfig;
 import net.sf.acegisecurity.MockHttpServletRequest;
 import net.sf.acegisecurity.MockHttpServletRequest;
@@ -130,6 +131,10 @@ public class BasicProcessingFilterTests extends TestCase {
         BasicProcessingFilter filter = new BasicProcessingFilter();
         BasicProcessingFilter filter = new BasicProcessingFilter();
         filter.setAuthenticationManager(new MockAuthenticationManager());
         filter.setAuthenticationManager(new MockAuthenticationManager());
         assertTrue(filter.getAuthenticationManager() != null);
         assertTrue(filter.getAuthenticationManager() != null);
+
+        filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
+                "sx"));
+        assertTrue(filter.getAuthenticationEntryPoint() != null);
     }
     }
 
 
     public void testInvalidBasicAuthorizationTokenIsIgnored()
     public void testInvalidBasicAuthorizationTokenIsIgnored()
@@ -228,10 +233,25 @@ public class BasicProcessingFilterTests extends TestCase {
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
     }
     }
 
 
+    public void testStartupDetectsMissingAuthenticationEntryPoint()
+        throws Exception {
+        try {
+            BasicProcessingFilter filter = new BasicProcessingFilter();
+            filter.setAuthenticationManager(new MockAuthenticationManager());
+            filter.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("An AuthenticationEntryPoint is required",
+                expected.getMessage());
+        }
+    }
+
     public void testStartupDetectsMissingAuthenticationManager()
     public void testStartupDetectsMissingAuthenticationManager()
         throws Exception {
         throws Exception {
         try {
         try {
             BasicProcessingFilter filter = new BasicProcessingFilter();
             BasicProcessingFilter filter = new BasicProcessingFilter();
+            filter.setAuthenticationEntryPoint(new MockAuthenticationEntryPoint(
+                    "x"));
             filter.afterPropertiesSet();
             filter.afterPropertiesSet();
             fail("Should have thrown IllegalArgumentException");
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
         } catch (IllegalArgumentException expected) {
@@ -293,7 +313,7 @@ public class BasicProcessingFilterTests extends TestCase {
             chain);
             chain);
 
 
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
-        assertEquals(403, response.getError());
+        assertEquals(401, response.getError());
     }
     }
 
 
     public void testWrongPasswordReturnsForbidden() throws Exception {
     public void testWrongPasswordReturnsForbidden() throws Exception {
@@ -325,7 +345,7 @@ public class BasicProcessingFilterTests extends TestCase {
             chain);
             chain);
 
 
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) == null);
-        assertEquals(403, response.getError());
+        assertEquals(401, response.getError());
     }
     }
 
 
     private void executeFilterInContainerSimulator(FilterConfig filterConfig,
     private void executeFilterInContainerSimulator(FilterConfig filterConfig,

+ 5 - 0
core/src/test/java/org/acegisecurity/ui/basicauth/filtertest-valid.xml

@@ -49,6 +49,11 @@
 
 
 	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
 	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
 		<property name="authenticationManager"><ref bean="authenticationManager"/></property>
 		<property name="authenticationManager"><ref bean="authenticationManager"/></property>
+		<property name="authenticationEntryPoint"><ref bean="basicProcessingFilterEntryPoint"/></property>
+	</bean>
+
+	<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
+		<property name="realmName"><value>Test Suite Realm</value></property>
 	</bean>
 	</bean>
 
 
 </beans>
 </beans>

+ 80 - 0
core/src/test/java/org/acegisecurity/ui/webapp/AuthenticationProcessingFilterEntryPointTests.java

@@ -0,0 +1,80 @@
+/* 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.ui.webapp;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.MockHttpServletRequest;
+import net.sf.acegisecurity.MockHttpServletResponse;
+
+
+/**
+ * Tests {@link AuthenticationProcessingFilterEntryPoint}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class AuthenticationProcessingFilterEntryPointTests extends TestCase {
+    //~ Constructors ===========================================================
+
+    public AuthenticationProcessingFilterEntryPointTests() {
+        super();
+    }
+
+    public AuthenticationProcessingFilterEntryPointTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(AuthenticationProcessingFilterEntryPointTests.class);
+    }
+
+    public void testDetectsMissingLoginFormUrl() throws Exception {
+        AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint();
+
+        try {
+            ep.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertEquals("loginFormUrl must be specified", expected.getMessage());
+        }
+    }
+
+    public void testGettersSetters() {
+        AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint();
+        ep.setLoginFormUrl("/hello");
+        assertEquals("/hello", ep.getLoginFormUrl());
+    }
+
+    public void testNormalOperation() throws Exception {
+        AuthenticationProcessingFilterEntryPoint ep = new AuthenticationProcessingFilterEntryPoint();
+        ep.setLoginFormUrl("/hello");
+
+        MockHttpServletRequest request = new MockHttpServletRequest(
+                "/some_path");
+        MockHttpServletResponse response = new MockHttpServletResponse();
+
+        ep.afterPropertiesSet();
+        ep.commence(request, response);
+        assertEquals("/hello", response.getRedirect());
+    }
+}

+ 42 - 24
docs/reference/src/index.xml

@@ -538,11 +538,15 @@
         so you should configure a <literal>ContextLoaderListener</literal> in
         so you should configure a <literal>ContextLoaderListener</literal> in
         <literal>web.xml</literal>.</para>
         <literal>web.xml</literal>.</para>
 
 
-        <para>In the application context you will need to configure two
+        <para>In the application context you will need to configure three
         beans:</para>
         beans:</para>
 
 
         <programlisting>&lt;bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter"&gt;
         <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="filterSecurityInterceptor"&gt;&lt;ref bean="filterInvocationInterceptor"/&gt;&lt;/property&gt;
+  &lt;property name="authenticationEntryPoint"&gt;&lt;ref bean="authenticationEntryPoint"/&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="loginFormUrl"&gt;&lt;value&gt;/acegilogin.jsp&lt;/value&gt;&lt;/property&gt;
 &lt;/bean&gt;
 &lt;/bean&gt;
 
 
@@ -559,16 +563,21 @@
   &lt;/property&gt;
   &lt;/property&gt;
 &lt;/bean&gt;</programlisting>
 &lt;/bean&gt;</programlisting>
 
 
-        <para>The <literal>loginFormUrl</literal> is where the filter will
-        redirect the user's browser if they request a secure HTTP resource but
-        they are not authenticated. If the user is authenticated, a "403
-        Forbidden" response will be returned to the browser. All paths are
-        relative to the web application root.</para>
+        <para>The <literal>AuthenticationEntryPoint</literal> will be called
+        if the user requests a secure HTTP resource but they are not
+        authenticated. The class handles presenting the appropriate response
+        to the user so that authentication can begin. Two concrete
+        implementations are provided with the Acegi Security System for
+        Spring: <literal>AuthenticationProcessingFilterEntryPoint</literal>
+        for commencing a form-based authentication, and
+        <literal>BasicProcessingFilterEntryPoint</literal> for commencing a
+        Http Basic authentication process.</para>
 
 
         <para>The <literal>SecurityEnforcementFilter</literal> primarily
         <para>The <literal>SecurityEnforcementFilter</literal> primarily
-        provides redirection and session management support. It delegates
-        actual <literal>FilterInvocation</literal> security decisions to the
-        configured <literal>FilterSecurityInterceptor</literal>.</para>
+        provides session management support and initiates authentication when
+        required. It delegates actual <literal>FilterInvocation</literal>
+        security decisions to the configured
+        <literal>FilterSecurityInterceptor</literal>.</para>
 
 
         <para>Like any other security interceptor, the
         <para>Like any other security interceptor, the
         <literal>FilterSecurityInterceptor</literal> requires a reference to
         <literal>FilterSecurityInterceptor</literal> requires a reference to
@@ -1560,19 +1569,18 @@ public boolean supports(Class clazz);</programlisting></para>
       <sect2 id="security-ui-http-basic">
       <sect2 id="security-ui-http-basic">
         <title>HTTP Basic Authentication</title>
         <title>HTTP Basic Authentication</title>
 
 
-        <para>Primarily to cater for the needs of remoting protocols such as
-        Hessian and Burlap, the Acegi Security System for Spring provides a
+        <para>The Acegi Security System for Spring provides a
         <literal>BasicProcessingFilter</literal> which is capable of
         <literal>BasicProcessingFilter</literal> which is capable of
-        processing authentication credentials presented in HTTP headers (for
-        standard authentication of web browser users, we recommend HTTP
-        Session Authentication). The standard governing HTTP Basic
+        processing authentication credentials presented in HTTP headers. This
+        can be used for authenticating calls made by Spring remoting protocols
+        (such as Hessian and Burlap), as well as normal user agents (such as
+        Internet Explorer and Navigator). The standard governing HTTP Basic
         Authentication is defined by RFC 1945, Section 11, and the
         Authentication is defined by RFC 1945, Section 11, and the
         <literal>BasicProcessingFilter</literal> conforms with this
         <literal>BasicProcessingFilter</literal> conforms with this
         RFC.</para>
         RFC.</para>
 
 
         <para>To implement HTTP Basic Authentication, it is necessary to add
         <para>To implement HTTP Basic Authentication, it is necessary to add
-        the following filter to <literal>web.xml</literal>, behind a
-        <literal>FilterToBeanProxy</literal>:</para>
+        the following filter to <literal>web.xml</literal>:</para>
 
 
         <para><programlisting>&lt;filter&gt;
         <para><programlisting>&lt;filter&gt;
   &lt;filter-name&gt;Acegi HTTP BASIC Authorization Filter&lt;/filter-name&gt;
   &lt;filter-name&gt;Acegi HTTP BASIC Authorization Filter&lt;/filter-name&gt;
@@ -1591,16 +1599,25 @@ public boolean supports(Class clazz);</programlisting></para>
         <para>For a discussion of <literal>FilterToBeanProxy</literal>, please
         <para>For a discussion of <literal>FilterToBeanProxy</literal>, please
         refer to the FilterInvocation Security Interceptor section. The
         refer to the FilterInvocation Security Interceptor section. The
         application context will need to define the
         application context will need to define the
-        <literal>BasicProcessingFilter</literal>:</para>
+        <literal>BasicProcessingFilter</literal> and its required
+        collaborator:</para>
 
 
         <para><programlisting>&lt;bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"&gt;
         <para><programlisting>&lt;bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter"&gt;
   &lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
   &lt;property name="authenticationManager"&gt;&lt;ref bean="authenticationManager"/&gt;&lt;/property&gt;
+  &lt;property name="authenticationEntryPoint"&gt;&lt;ref bean="authenticationEntryPoint"/&gt;&lt;/property&gt;
+&lt;/bean&gt;
+
+&lt;bean id="authenticationEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint"&gt;
+  &lt;property name="realmName"&gt;&lt;value&gt;Name Of Your Realm&lt;/value&gt;&lt;/property&gt;
 &lt;/bean&gt;</programlisting></para>
 &lt;/bean&gt;</programlisting></para>
 
 
         <para>The configured <literal>AuthenticationManager</literal>
         <para>The configured <literal>AuthenticationManager</literal>
-        processes each authentication request. If authentication fails, a 403
-        (forbidden) response will be returned in response to the HTTP request.
-        If authentication is successful, the resulting
+        processes each authentication request. If authentication fails, the
+        configured <literal>AuthenticationEntryPoint</literal> will be used to
+        retry the authentication process. Usually you will use the
+        <literal>BasicProcessingFilterEntryPoint</literal>, which returns a
+        401 response with a suitable header to retry HTTP Basic
+        authentication. If authentication is successful, the resulting
         <literal>Authentication</literal> object will be placed into the
         <literal>Authentication</literal> object will be placed into the
         <literal>HttpSession</literal> attribute indicated by
         <literal>HttpSession</literal> attribute indicated by
         <literal>HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY</literal>.
         <literal>HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY</literal>.
@@ -1611,13 +1628,14 @@ public boolean supports(Class clazz);</programlisting></para>
         was not attempted because the HTTP header did not contain a supported
         was not attempted because the HTTP header did not contain a supported
         authentication request, the filter chain will continue as normal. The
         authentication request, the filter chain will continue as normal. The
         only time the filter chain will be interrupted is if authentication
         only time the filter chain will be interrupted is if authentication
-        fails and a 403 response is returned, as discussed in the previous
-        paragraph.</para>
+        fails and the <literal>AuthenticationEntryPoint</literal> is called,
+        as discussed in the previous paragraph.</para>
 
 
         <para>HTTP Basic Authentication is recommended to be used instead of
         <para>HTTP Basic Authentication is recommended to be used instead of
         Container Adapters. It can be used in conjunction with HTTP Session
         Container Adapters. It can be used in conjunction with HTTP Session
-        Authentication, as demonstrated in the Contacts sample
-        application.</para>
+        Authentication, as demonstrated in the Contacts sample application.
+        You can also use it instead of HTTP Session Authentication if you
+        wish.</para>
       </sect2>
       </sect2>
 
 
       <sect2 id="security-ui-well-known">
       <sect2 id="security-ui-well-known">

+ 5 - 0
samples/contacts/etc/ca/applicationContext.xml

@@ -52,6 +52,11 @@
 
 
 	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
 	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
 		<property name="authenticationManager"><ref bean="authenticationManager"/></property>
 		<property name="authenticationManager"><ref bean="authenticationManager"/></property>
+		<property name="authenticationEntryPoint"><ref bean="basicProcessingFilterEntryPoint"/></property>
+	</bean>
+
+	<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
+		<property name="realmName"><value>Contacts Realm</value></property>
 	</bean>
 	</bean>
 
 
 	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
 	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->

+ 9 - 0
samples/contacts/etc/filter/applicationContext.xml

@@ -47,6 +47,11 @@
 
 
 	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
 	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">
 		<property name="authenticationManager"><ref bean="authenticationManager"/></property>
 		<property name="authenticationManager"><ref bean="authenticationManager"/></property>
+		<property name="authenticationEntryPoint"><ref bean="basicProcessingFilterEntryPoint"/></property>
+	</bean>
+
+	<bean id="basicProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
+		<property name="realmName"><value>Contacts Realm</value></property>
 	</bean>
 	</bean>
 
 
 	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
 	<!-- ~~~~~~~~~~~~~~~~~~~~ AUTHORIZATION DEFINITIONS ~~~~~~~~~~~~~~~~~~~ -->
@@ -138,6 +143,10 @@
 
 
 	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
 	<bean id="securityEnforcementFilter" class="net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter">
 		<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
 		<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
+		<property name="authenticationEntryPoint"><ref bean="authenticationProcessingFilterEntryPoint"/></property>
+	</bean>
+
+	<bean id="authenticationProcessingFilterEntryPoint" class="net.sf.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
 		<property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
 		<property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
 	</bean>
 	</bean>