Avoid expense of HttpSession when working with anonymous users.

This commit is contained in:
Ben Alex 2005-07-23 09:52:42 +00:00
parent 4ad98a7df3
commit f625d06cd9
4 changed files with 65 additions and 69 deletions

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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>

View File

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