瀏覽代碼

Avoid expense of HttpSession when working with anonymous users.

Ben Alex 20 年之前
父節點
當前提交
f625d06cd9

+ 43 - 60
core/src/main/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilter.java

@@ -12,7 +12,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package net.sf.acegisecurity.providers.anonymous;
 
 import net.sf.acegisecurity.Authentication;
@@ -38,49 +37,8 @@ import javax.servlet.ServletResponse;
 
 /**
  * Detects if there is no <code>Authentication</code> object in the
- * <code>ContextHolder</code>,  and populates it with one if needed.
- * 
- * <P></p>
- * 
- * <p>
- * In summary, this filter is responsible for processing any request that has a
- * HTTP request header of <code>Authorization</code> with an authentication
- * scheme of <code>Basic</code> and a Base64-encoded
- * <code>username:password</code> token. For example, to authenticate user
- * "Aladdin" with password "open sesame" the following header would be
- * presented:
- * </p>
- * 
- * <p>
- * <code>Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==</code>.
- * </p>
- * 
- * <p>
- * 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>
- * If authentication is successful, the resulting {@link Authentication} object
- * will be placed into the <code>ContextHolder</code>.
- * </p>
- * 
- * <p>
- * 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>
- * Basic authentication is an attractive protocol because it is simple and
- * widely deployed. However, it still transmits a password in clear text and
- * as such is undesirable in many situations. Digest authentication is also
- * provided by Acegi Security and should be used instead of Basic
- * authentication wherever possible. See {@link
- * net.sf.acegisecurity.ui.digestauth.DigestProcessingFilter}.
- * </p>
- * 
+ * <code>SecurityContextHolder</code>,  and populates it with one if needed.
+ *
  * <P>
  * <B>Do not use this class directly.</B> Instead configure
  * <code>web.xml</code> to use the {@link
@@ -91,16 +49,10 @@ import javax.servlet.ServletResponse;
  * @version $Id$
  */
 public class AnonymousProcessingFilter implements Filter, InitializingBean {
-    //~ Static fields/initializers =============================================
-
     private static final Log logger = LogFactory.getLog(AnonymousProcessingFilter.class);
-
-    //~ Instance fields ========================================================
-
     private String key;
     private UserAttribute userAttribute;
-
-    //~ Methods ================================================================
+    private boolean removeAfterRequest = true;
 
     public void setKey(String key) {
         this.key = key;
@@ -126,32 +78,42 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
     /**
      * Does nothing - we reply on IoC lifecycle services instead.
      */
-    public void destroy() {}
+    public void destroy() {
+    }
 
     public void doFilter(ServletRequest request, ServletResponse response,
         FilterChain chain) throws IOException, ServletException {
+        boolean addedToken = false;
+
         if (applyAnonymousForThisRequest(request)) {
             if (SecurityContextHolder.getContext().getAuthentication() == null) {
                 SecurityContextHolder.getContext().setAuthentication(createAuthentication(
                         request));
+                addedToken = true;
 
                 if (logger.isDebugEnabled()) {
                     logger.debug(
-                        "Replaced SecurityContextHolder with anonymous token: '"
-                        + SecurityContextHolder.getContext().getAuthentication()
-                        + "'");
+                        "Replaced SecurityContextHolder with anonymous token: '" +
+                        SecurityContextHolder.getContext().getAuthentication() +
+                        "'");
                 }
             } else {
                 if (logger.isDebugEnabled()) {
                     logger.debug(
-                        "SecurityContextHolder not replaced with anonymous token, as ContextHolder already contained: '"
-                        + SecurityContextHolder.getContext().getAuthentication()
-                        + "'");
+                        "SecurityContextHolder not replaced with anonymous token, as ContextHolder already contained: '" +
+                        SecurityContextHolder.getContext().getAuthentication() +
+                        "'");
                 }
             }
         }
 
-        chain.doFilter(request, response);
+        try {
+            chain.doFilter(request, response);
+        } finally {
+            if (addedToken && removeAfterRequest) {
+                SecurityContextHolder.getContext().setAuthentication(null);
+            }
+        }
     }
 
     /**
@@ -161,7 +123,8 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
      *
      * @throws ServletException DOCUMENT ME!
      */
-    public void init(FilterConfig arg0) throws ServletException {}
+    public void init(FilterConfig arg0) throws ServletException {
+    }
 
     /**
      * Enables subclasses to determine whether or not an anonymous
@@ -185,4 +148,24 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
         return new AnonymousAuthenticationToken(key,
             userAttribute.getPassword(), userAttribute.getAuthorities());
     }
+
+    public boolean isRemoveAfterRequest() {
+        return removeAfterRequest;
+    }
+
+    /**
+     * Controls whether the filter will remove the Anonymous token
+     * after the request is complete. Generally this is desired to
+     * avoid the expense of a session being created by
+     * {@link net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter} simply
+     * to store the Anonymous authentication token.
+     *
+     * <p>Defaults to <code>true</code>,
+     * being the most optimal and appropriate option (ie <code>AnonymousProcessingFilter</code>
+     * will clear the token at the end of each request, thus avoiding the session creation
+     * overhead in a typical configuration.
+     */
+    public void setRemoveAfterRequest(boolean removeAfterRequest) {
+        this.removeAfterRequest = removeAfterRequest;
+    }
 }

+ 13 - 9
core/src/test/java/org/acegisecurity/providers/anonymous/AnonymousProcessingFilterTests.java

@@ -12,7 +12,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package net.sf.acegisecurity.providers.anonymous;
 
 import junit.framework.TestCase;
@@ -46,8 +45,6 @@ import javax.servlet.ServletResponse;
  * @version $Id$
  */
 public class AnonymousProcessingFilterTests extends TestCase {
-    //~ Constructors ===========================================================
-
     public AnonymousProcessingFilterTests() {
         super();
     }
@@ -56,8 +53,6 @@ public class AnonymousProcessingFilterTests extends TestCase {
         super(arg0);
     }
 
-    //~ Methods ================================================================
-
     public static void main(String[] args) {
         junit.textui.TestRunner.run(AnonymousProcessingFilterTests.class);
     }
@@ -98,10 +93,13 @@ public class AnonymousProcessingFilterTests extends TestCase {
         AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
         filter.setKey("qwerty");
         filter.setUserAttribute(user);
+        assertTrue(filter.isRemoveAfterRequest());
         filter.afterPropertiesSet();
 
         assertEquals("qwerty", filter.getKey());
         assertEquals(user, filter.getUserAttribute());
+        filter.setRemoveAfterRequest(false);
+        assertFalse(filter.isRemoveAfterRequest());
     }
 
     public void testOperationWhenAuthenticationExistsInContextHolder()
@@ -109,7 +107,7 @@ public class AnonymousProcessingFilterTests extends TestCase {
         // Put an Authentication object into the ContextHolder
         Authentication originalAuth = new TestingAuthenticationToken("user",
                 "password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")});
+                new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A") });
         SecurityContextHolder.getContext().setAuthentication(originalAuth);
 
         // Setup our filter correctly
@@ -133,7 +131,7 @@ public class AnonymousProcessingFilterTests extends TestCase {
             SecurityContextHolder.getContext().getAuthentication());
     }
 
-    public void testOperationWhenNoAuthenticationInContextHolder()
+    public void testOperationWhenNoAuthenticationInSecurityContextHolder()
         throws Exception {
         UserAttribute user = new UserAttribute();
         user.setPassword("anonymousUsername");
@@ -142,6 +140,7 @@ public class AnonymousProcessingFilterTests extends TestCase {
         AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
         filter.setKey("qwerty");
         filter.setUserAttribute(user);
+        filter.setRemoveAfterRequest(false); // set to non-default value
         filter.afterPropertiesSet();
 
         MockHttpServletRequest request = new MockHttpServletRequest();
@@ -154,6 +153,13 @@ public class AnonymousProcessingFilterTests extends TestCase {
         assertEquals("anonymousUsername", auth.getPrincipal());
         assertEquals(new GrantedAuthorityImpl("ROLE_ANONYMOUS"),
             auth.getAuthorities()[0]);
+        SecurityContextHolder.getContext().setAuthentication(null); // so anonymous fires again
+
+        // Now test operation if we have removeAfterRequest = true
+        filter.setRemoveAfterRequest(true); // set to default value
+        executeFilterInContainerSimulator(new MockFilterConfig(), filter,
+            request, new MockHttpServletResponse(), new MockFilterChain(true));
+        assertNull(SecurityContextHolder.getContext().getAuthentication());
     }
 
     protected void setUp() throws Exception {
@@ -174,8 +180,6 @@ public class AnonymousProcessingFilterTests extends TestCase {
         filter.destroy();
     }
 
-    //~ Inner Classes ==========================================================
-
     private class MockFilterChain implements FilterChain {
         private boolean expectToProceed;
 

+ 6 - 0
doc/xdocs/upgrade/upgrade-080-090.html

@@ -40,6 +40,12 @@ applications:
     authentication exception directions. See the <a href="../multiproject/acegi-security/xref/net/sf/acegisecurity/ui/AbstractProcessingFilter.html">
     AbstractProcessingFilter JavaDocs</a> to learn more.<br><br></li>
     
+    <li>AnonymousProcessingFilter now has a removeAfterRequest property, which defaults to true. This
+    will cause the anonymous authentication token to be set to null at the end of each request, thus
+    avoiding the expense of creating a HttpSession in HttpSessionContextIntegrationFilter. You may
+    set this property to false if you would like the anoymous authentication token to be preserved,
+    which would be an unusual requirement.<br><br></li>
+    
     </ul>
 
 </body>

+ 3 - 0
project.xml

@@ -147,6 +147,9 @@
     <contributor>
       <name>Paulo Neves</name>
     </contributor>
+    <contributor>
+      <name>Mike Perham</name>
+    </contributor>
   </contributors>
   <dependencies>
     <dependency>