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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.sf.acegisecurity.providers.anonymous; package net.sf.acegisecurity.providers.anonymous;
import net.sf.acegisecurity.Authentication; import net.sf.acegisecurity.Authentication;
@ -38,49 +37,8 @@ import javax.servlet.ServletResponse;
/** /**
* Detects if there is no <code>Authentication</code> object in the * Detects if there is no <code>Authentication</code> object in the
* <code>ContextHolder</code>, and populates it with one if needed. * <code>SecurityContextHolder</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>
*
* <P> * <P>
* <B>Do not use this class directly.</B> Instead configure * <B>Do not use this class directly.</B> Instead configure
* <code>web.xml</code> to use the {@link * <code>web.xml</code> to use the {@link
@ -91,16 +49,10 @@ import javax.servlet.ServletResponse;
* @version $Id$ * @version $Id$
*/ */
public class AnonymousProcessingFilter implements Filter, InitializingBean { public class AnonymousProcessingFilter implements Filter, InitializingBean {
//~ Static fields/initializers =============================================
private static final Log logger = LogFactory.getLog(AnonymousProcessingFilter.class); private static final Log logger = LogFactory.getLog(AnonymousProcessingFilter.class);
//~ Instance fields ========================================================
private String key; private String key;
private UserAttribute userAttribute; private UserAttribute userAttribute;
private boolean removeAfterRequest = true;
//~ Methods ================================================================
public void setKey(String key) { public void setKey(String key) {
this.key = key; this.key = key;
@ -126,32 +78,42 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
/** /**
* Does nothing - we reply on IoC lifecycle services instead. * Does nothing - we reply on IoC lifecycle services instead.
*/ */
public void destroy() {} public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response, public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException { FilterChain chain) throws IOException, ServletException {
boolean addedToken = false;
if (applyAnonymousForThisRequest(request)) { if (applyAnonymousForThisRequest(request)) {
if (SecurityContextHolder.getContext().getAuthentication() == null) { if (SecurityContextHolder.getContext().getAuthentication() == null) {
SecurityContextHolder.getContext().setAuthentication(createAuthentication( SecurityContextHolder.getContext().setAuthentication(createAuthentication(
request)); request));
addedToken = true;
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug( logger.debug(
"Replaced SecurityContextHolder with anonymous token: '" "Replaced SecurityContextHolder with anonymous token: '" +
+ SecurityContextHolder.getContext().getAuthentication() SecurityContextHolder.getContext().getAuthentication() +
+ "'"); "'");
} }
} else { } else {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug( logger.debug(
"SecurityContextHolder not replaced with anonymous token, as ContextHolder already contained: '" "SecurityContextHolder not replaced with anonymous token, as ContextHolder already contained: '" +
+ SecurityContextHolder.getContext().getAuthentication() 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! * @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 * Enables subclasses to determine whether or not an anonymous
@ -185,4 +148,24 @@ public class AnonymousProcessingFilter implements Filter, InitializingBean {
return new AnonymousAuthenticationToken(key, return new AnonymousAuthenticationToken(key,
userAttribute.getPassword(), userAttribute.getAuthorities()); 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 * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package net.sf.acegisecurity.providers.anonymous; package net.sf.acegisecurity.providers.anonymous;
import junit.framework.TestCase; import junit.framework.TestCase;
@ -46,8 +45,6 @@ import javax.servlet.ServletResponse;
* @version $Id$ * @version $Id$
*/ */
public class AnonymousProcessingFilterTests extends TestCase { public class AnonymousProcessingFilterTests extends TestCase {
//~ Constructors ===========================================================
public AnonymousProcessingFilterTests() { public AnonymousProcessingFilterTests() {
super(); super();
} }
@ -56,8 +53,6 @@ public class AnonymousProcessingFilterTests extends TestCase {
super(arg0); super(arg0);
} }
//~ Methods ================================================================
public static void main(String[] args) { public static void main(String[] args) {
junit.textui.TestRunner.run(AnonymousProcessingFilterTests.class); junit.textui.TestRunner.run(AnonymousProcessingFilterTests.class);
} }
@ -98,10 +93,13 @@ public class AnonymousProcessingFilterTests extends TestCase {
AnonymousProcessingFilter filter = new AnonymousProcessingFilter(); AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
filter.setKey("qwerty"); filter.setKey("qwerty");
filter.setUserAttribute(user); filter.setUserAttribute(user);
assertTrue(filter.isRemoveAfterRequest());
filter.afterPropertiesSet(); filter.afterPropertiesSet();
assertEquals("qwerty", filter.getKey()); assertEquals("qwerty", filter.getKey());
assertEquals(user, filter.getUserAttribute()); assertEquals(user, filter.getUserAttribute());
filter.setRemoveAfterRequest(false);
assertFalse(filter.isRemoveAfterRequest());
} }
public void testOperationWhenAuthenticationExistsInContextHolder() public void testOperationWhenAuthenticationExistsInContextHolder()
@ -109,7 +107,7 @@ public class AnonymousProcessingFilterTests extends TestCase {
// Put an Authentication object into the ContextHolder // Put an Authentication object into the ContextHolder
Authentication originalAuth = new TestingAuthenticationToken("user", Authentication originalAuth = new TestingAuthenticationToken("user",
"password", "password",
new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A")}); new GrantedAuthority[] { new GrantedAuthorityImpl("ROLE_A") });
SecurityContextHolder.getContext().setAuthentication(originalAuth); SecurityContextHolder.getContext().setAuthentication(originalAuth);
// Setup our filter correctly // Setup our filter correctly
@ -133,7 +131,7 @@ public class AnonymousProcessingFilterTests extends TestCase {
SecurityContextHolder.getContext().getAuthentication()); SecurityContextHolder.getContext().getAuthentication());
} }
public void testOperationWhenNoAuthenticationInContextHolder() public void testOperationWhenNoAuthenticationInSecurityContextHolder()
throws Exception { throws Exception {
UserAttribute user = new UserAttribute(); UserAttribute user = new UserAttribute();
user.setPassword("anonymousUsername"); user.setPassword("anonymousUsername");
@ -142,6 +140,7 @@ public class AnonymousProcessingFilterTests extends TestCase {
AnonymousProcessingFilter filter = new AnonymousProcessingFilter(); AnonymousProcessingFilter filter = new AnonymousProcessingFilter();
filter.setKey("qwerty"); filter.setKey("qwerty");
filter.setUserAttribute(user); filter.setUserAttribute(user);
filter.setRemoveAfterRequest(false); // set to non-default value
filter.afterPropertiesSet(); filter.afterPropertiesSet();
MockHttpServletRequest request = new MockHttpServletRequest(); MockHttpServletRequest request = new MockHttpServletRequest();
@ -154,6 +153,13 @@ public class AnonymousProcessingFilterTests extends TestCase {
assertEquals("anonymousUsername", auth.getPrincipal()); assertEquals("anonymousUsername", auth.getPrincipal());
assertEquals(new GrantedAuthorityImpl("ROLE_ANONYMOUS"), assertEquals(new GrantedAuthorityImpl("ROLE_ANONYMOUS"),
auth.getAuthorities()[0]); 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 { protected void setUp() throws Exception {
@ -174,8 +180,6 @@ public class AnonymousProcessingFilterTests extends TestCase {
filter.destroy(); filter.destroy();
} }
//~ Inner Classes ==========================================================
private class MockFilterChain implements FilterChain { private class MockFilterChain implements FilterChain {
private boolean expectToProceed; 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"> 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> 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> </ul>
</body> </body>

View File

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