瀏覽代碼

Refactor DaoAuthenticationProvider cache model.

Ben Alex 21 年之前
父節點
當前提交
1b24ff5ea8
共有 19 個文件被更改,包括 518 次插入601 次删除
  1. 0 1
      adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-invalid.xml
  2. 0 1
      adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-valid.xml
  3. 68 84
      core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java
  4. 0 156
      core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationToken.java
  5. 53 0
      core/src/main/java/org/acegisecurity/providers/dao/UserCache.java
  6. 135 0
      core/src/main/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCache.java
  7. 36 0
      core/src/main/java/org/acegisecurity/providers/dao/cache/NullUserCache.java
  8. 5 0
      core/src/main/java/org/acegisecurity/providers/dao/cache/package.html
  9. 0 1
      core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml
  10. 39 109
      core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java
  11. 0 226
      core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationTokenTests.java
  12. 83 0
      core/src/test/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCacheTests.java
  13. 63 0
      core/src/test/java/org/acegisecurity/providers/dao/cache/NullUserCacheTests.java
  14. 0 1
      core/src/test/java/org/acegisecurity/ui/basicauth/filtertest-valid.xml
  15. 24 18
      docs/reference/src/index.xml
  16. 2 0
      samples/contacts/build.xml
  17. 0 1
      samples/contacts/etc/ca/applicationContext.xml
  18. 5 1
      samples/contacts/etc/filter/applicationContext.xml
  19. 5 2
      samples/quick-start/war-root/WEB-INF/applicationContext.xml

+ 0 - 1
adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-invalid.xml

@@ -33,7 +33,6 @@
 	
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
-		<property name="key"><value>my_password</value></property>
  	</bean>
 
 	<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">

+ 0 - 1
adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-valid.xml

@@ -33,7 +33,6 @@
 	
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
-		<property name="key"><value>my_password</value></property>
 	</bean>
 
 	<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">

+ 68 - 84
core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java

@@ -22,6 +22,7 @@ import net.sf.acegisecurity.BadCredentialsException;
 import net.sf.acegisecurity.DisabledException;
 import net.sf.acegisecurity.providers.AuthenticationProvider;
 import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
 import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureDisabledEvent;
 import net.sf.acegisecurity.providers.dao.event.AuthenticationFailurePasswordEvent;
 import net.sf.acegisecurity.providers.dao.event.AuthenticationSuccessEvent;
@@ -36,32 +37,26 @@ import org.springframework.context.ApplicationContextAware;
 
 import org.springframework.dao.DataAccessException;
 
-import java.util.Date;
-
 
 /**
  * An {@link AuthenticationProvider} implementation that retrieves user details
  * from an {@link AuthenticationDao}.
  * 
  * <p>
- * This <code>AuthenticationProvider</code> is capable of validating  {@link
+ * This <code>AuthenticationProvider</code> is capable of validating {@link
  * UsernamePasswordAuthenticationToken} requests contain the correct username,
  * password and the user is not disabled.
  * </p>
  * 
  * <p>
- * Upon successful validation, a <code>DaoAuthenticationToken</code> will be
- * created and returned to the caller. This token will be signed with the key
- * configured by {@link #getKey()} and expire {@link
- * #getRefreshTokenInterval()} milliseconds into the future. The token will be
- * assumed to remain valid whilstever it has not expired, and no requests of
- * the <code>AuthenticationProvider</code> will need to be made. Once the
- * token has expired, the relevant <code>AuthenticationProvider</code> will be
- * called again to provide an updated enabled/disabled status, and list of
- * granted authorities. It should be noted the credentials will not be
- * revalidated, as the user presented correct credentials in the originial
- * <code>UsernamePasswordAuthenticationToken</code>. This avoids complications
- * if the user changes their password during the session.
+ * Upon successful validation, a
+ * <code>UsernamePasswordAuthenticationToken</code> will be created and
+ * returned to the caller. In addition, the {@link User} will be placed in the
+ * {@link UserCache} so that subsequent requests with the same username can be
+ * validated without needing to query the {@link AuthenticationDao}. It should
+ * be noted that if a user appears to present an incorrect password, the
+ * {@link AuthenticationDao} will be queried to confirm the most up-to-date
+ * password was used for comparison.
  * </p>
  * 
  * <P>
@@ -83,8 +78,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
     private AuthenticationDao authenticationDao;
     private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();
     private SaltSource saltSource;
-    private String key;
-    private long refreshTokenInterval = 60000; // 60 seconds
+    private UserCache userCache = new NullUserCache();
 
     //~ Methods ================================================================
 
@@ -101,14 +95,6 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
         return authenticationDao;
     }
 
-    public void setKey(String key) {
-        this.key = key;
-    }
-
-    public String getKey() {
-        return key;
-    }
-
     /**
      * Sets the PasswordEncoder instance to be used to encode and validate
      * passwords. If not set, {@link PlaintextPasswordEncoder} will be used by
@@ -124,22 +110,6 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
         return passwordEncoder;
     }
 
-    public void setRefreshTokenInterval(long refreshTokenInterval) {
-        this.refreshTokenInterval = refreshTokenInterval;
-    }
-
-    /**
-     * Indicates the number of seconds a created
-     * <code>DaoAuthenticationToken</code> will remain valid for. Whilstever
-     * the token is valid, the <code>DaoAuthenticationProvider</code> will
-     * only check it presents the expected key hash code.
-     *
-     * @return Returns the refreshTokenInterval.
-     */
-    public long getRefreshTokenInterval() {
-        return refreshTokenInterval;
-    }
-
     /**
      * The source of salts to use when decoding passwords.  <code>null</code>
      * is a valid value, meaning the <code>DaoAuthenticationProvider</code>
@@ -157,46 +127,36 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
         return saltSource;
     }
 
+    public void setUserCache(UserCache userCache) {
+        this.userCache = userCache;
+    }
+
+    public UserCache getUserCache() {
+        return userCache;
+    }
+
     public void afterPropertiesSet() throws Exception {
         if (this.authenticationDao == null) {
             throw new IllegalArgumentException(
                 "An Authentication DAO must be set");
         }
 
-        if ((this.key == null) || "".equals(key)) {
-            throw new IllegalArgumentException("A key must be set");
+        if (this.userCache == null) {
+            throw new IllegalArgumentException("A user cache must be set");
         }
     }
 
     public Authentication authenticate(Authentication authentication)
         throws AuthenticationException {
-        // If an existing DaoAuthenticationToken, check we created it and it hasn't expired
-        if (authentication instanceof DaoAuthenticationToken) {
-            if (this.key.hashCode() == ((DaoAuthenticationToken) authentication)
-                .getKeyHash()) {
-                if (((DaoAuthenticationToken) authentication).getExpires()
-                     .after(new Date())) {
-                    return authentication;
-                }
-            } else {
-                throw new BadCredentialsException(
-                    "The presented DaoAuthenticationToken does not contain the expected key");
-            }
-        }
-
-        // We need to authenticate or refresh the token
-        User user = null;
+        boolean cacheWasUsed = true;
+        User user = this.userCache.getUserFromCache(authentication.getPrincipal()
+                                                                  .toString());
 
-        try {
-            user = this.authenticationDao.loadUserByUsername(authentication.getPrincipal()
-                                                                           .toString());
-        } catch (UsernameNotFoundException notFound) {
-            throw new BadCredentialsException("Bad credentials presented");
-        } catch (DataAccessException repositoryProblem) {
-            throw new AuthenticationServiceException(repositoryProblem
-                .getMessage(), repositoryProblem);
+        if (user == null) {
+            cacheWasUsed = false;
+            user = getUserFromBackend(authentication);
         }
-        
+
         if (!user.isEnabled()) {
             if (this.ctx != null) {
                 ctx.publishEvent(new AuthenticationFailureDisabledEvent(
@@ -206,16 +166,14 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
             throw new DisabledException("User is disabled");
         }
 
-        if (!(authentication instanceof DaoAuthenticationToken)) {
-            // Must validate credentials, as this is not simply a token refresh
-            Object salt = null;
-
-            if (this.saltSource != null) {
-                salt = this.saltSource.getSalt(user);
+        if (!isPasswordCorrect(authentication, user)) {
+            // Password incorrect, so ensure we're using most current password
+            if (cacheWasUsed) {
+                cacheWasUsed = false;
+                user = getUserFromBackend(authentication);
             }
 
-            if (!passwordEncoder.isPasswordValid(user.getPassword(),
-                    authentication.getCredentials().toString(), salt)) {
+            if (!isPasswordCorrect(authentication, user)) {
                 if (this.ctx != null) {
                     ctx.publishEvent(new AuthenticationFailurePasswordEvent(
                             authentication, user));
@@ -225,24 +183,50 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
             }
         }
 
-        Date expiry = new Date(new Date().getTime()
-                + this.getRefreshTokenInterval());
+        if (!cacheWasUsed) {
+            // Put into cache
+            this.userCache.putUserInCache(user);
 
-        if (this.ctx != null) {
-            ctx.publishEvent(new AuthenticationSuccessEvent(authentication, user));
+            // As this appears to be an initial login, publish the event
+            if (this.ctx != null) {
+                ctx.publishEvent(new AuthenticationSuccessEvent(
+                        authentication, user));
+            }
         }
 
-        return new DaoAuthenticationToken(this.getKey(), expiry,
-            user.getUsername(), user.getPassword(), user.getAuthorities());
+        return new UsernamePasswordAuthenticationToken(user.getUsername(),
+            user.getPassword(), user.getAuthorities());
     }
 
     public boolean supports(Class authentication) {
         if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(
-                authentication)
-            || (DaoAuthenticationToken.class.isAssignableFrom(authentication))) {
+                authentication)) {
             return true;
         } else {
             return false;
         }
     }
+
+    private boolean isPasswordCorrect(Authentication authentication, User user) {
+        Object salt = null;
+
+        if (this.saltSource != null) {
+            salt = this.saltSource.getSalt(user);
+        }
+
+        return passwordEncoder.isPasswordValid(user.getPassword(),
+            authentication.getCredentials().toString(), salt);
+    }
+
+    private User getUserFromBackend(Authentication authentication) {
+        try {
+            return this.authenticationDao.loadUserByUsername(authentication.getPrincipal()
+                                                                           .toString());
+        } catch (UsernameNotFoundException notFound) {
+            throw new BadCredentialsException("Bad credentials presented");
+        } catch (DataAccessException repositoryProblem) {
+            throw new AuthenticationServiceException(repositoryProblem
+                .getMessage(), repositoryProblem);
+        }
+    }
 }

+ 0 - 156
core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationToken.java

@@ -1,156 +0,0 @@
-/* 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.providers.dao;
-
-import net.sf.acegisecurity.GrantedAuthority;
-import net.sf.acegisecurity.providers.AbstractAuthenticationToken;
-
-import java.io.Serializable;
-
-import java.util.Date;
-
-
-/**
- * Represents a successful DAO-based <code>Authentication</code>.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class DaoAuthenticationToken extends AbstractAuthenticationToken
-    implements Serializable {
-    //~ Instance fields ========================================================
-
-    private Date expires;
-    private Object credentials;
-    private Object principal;
-    private GrantedAuthority[] authorities;
-    private int keyHash;
-
-    //~ Constructors ===========================================================
-
-    /**
-     * Constructor.
-     *
-     * @param key to identify if this object made by a given {@link
-     *        DaoAuthenticationProvider}
-     * @param expires when the token is due to expire
-     * @param principal the username from the {@link User} object
-     * @param credentials the password from the {@link User} object
-     * @param authorities the authorities granted to the user, from the {@link
-     *        User} object
-     *
-     * @throws IllegalArgumentException if a <code>null</code> was passed
-     */
-    public DaoAuthenticationToken(String key, Date expires, Object principal,
-        Object credentials, GrantedAuthority[] authorities) {
-        if ((key == null) || ("".equals(key)) || (expires == null)
-            || (principal == null) || "".equals(principal)
-            || (credentials == null) || "".equals(credentials)
-            || (authorities == null)) {
-            throw new IllegalArgumentException(
-                "Cannot pass null or empty values to constructor");
-        }
-
-        for (int i = 0; i < authorities.length; i++) {
-            if (authorities[i] == null) {
-                throw new IllegalArgumentException("Granted authority element "
-                    + i
-                    + " is null - GrantedAuthority[] cannot contain any null elements");
-            }
-        }
-
-        this.keyHash = key.hashCode();
-        this.expires = expires;
-        this.principal = principal;
-        this.credentials = credentials;
-        this.authorities = authorities;
-    }
-
-    protected DaoAuthenticationToken() {
-        throw new IllegalArgumentException("Cannot use default constructor");
-    }
-
-    //~ Methods ================================================================
-
-    /**
-     * Ignored (always <code>true</code>).
-     *
-     * @param isAuthenticated ignored
-     */
-    public void setAuthenticated(boolean isAuthenticated) {
-        // ignored
-    }
-
-    /**
-     * Always returns <code>true</code>.
-     *
-     * @return true
-     */
-    public boolean isAuthenticated() {
-        return true;
-    }
-
-    public GrantedAuthority[] getAuthorities() {
-        return this.authorities;
-    }
-
-    public Object getCredentials() {
-        return this.credentials;
-    }
-
-    public Date getExpires() {
-        return this.expires;
-    }
-
-    public int getKeyHash() {
-        return this.keyHash;
-    }
-
-    public Object getPrincipal() {
-        return this.principal;
-    }
-
-    public boolean equals(Object obj) {
-        if (!super.equals(obj)) {
-            return false;
-        }
-
-        if (obj instanceof DaoAuthenticationToken) {
-            DaoAuthenticationToken test = (DaoAuthenticationToken) obj;
-
-            if (this.getKeyHash() != test.getKeyHash()) {
-                return false;
-            }
-
-            // expires never null due to constructor
-            if (this.getExpires() != test.getExpires()) {
-                return false;
-            }
-
-            return true;
-        }
-
-        return false;
-    }
-
-    public String toString() {
-        StringBuffer sb = new StringBuffer();
-        sb.append(super.toString());
-        sb.append("; Expires: " + this.expires.toString());
-
-        return sb.toString();
-    }
-}

+ 53 - 0
core/src/main/java/org/acegisecurity/providers/dao/UserCache.java

@@ -0,0 +1,53 @@
+/* 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.providers.dao;
+
+/**
+ * Provides a cache of {@link User} objects.
+ * 
+ * <P>
+ * Implementations should provide appropriate methods to set their cache
+ * parameters (eg time-to-live) and/or force removal of entities before their
+ * normal expiration. These are not part of the <code>UserCache</code>
+ * interface contract because they vary depending on the type of caching
+ * system used (eg in-memory vs disk vs cluster vs hybrid).
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface UserCache {
+    //~ Methods ================================================================
+
+    /**
+     * Obtains a {@link User} from the cache.
+     *
+     * @param username the {@link User#getUsername()} used to place the user in
+     *        the cache
+     *
+     * @return the populated <code>User</code> or <code>null</code> if the user
+     *         could not be found or if the cache entry has expired
+     */
+    public User getUserFromCache(String username);
+
+    /**
+     * Places a {@link User} in the cache. The <code>username</code> is the key
+     * used to subsequently retrieve the <code>User</code>.
+     *
+     * @param user the fully populated <code>User</code> to place in the cache
+     */
+    public void putUserInCache(User user);
+}

+ 135 - 0
core/src/main/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCache.java

@@ -0,0 +1,135 @@
+/* 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.providers.dao.cache;
+
+import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserCache;
+
+import net.sf.ehcache.Cache;
+import net.sf.ehcache.CacheException;
+import net.sf.ehcache.CacheManager;
+import net.sf.ehcache.Element;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.dao.DataRetrievalFailureException;
+
+
+/**
+ * Caches <code>User</code> objects using <A
+ * HREF="http://ehcache.sourceforge.net">EHCACHE</a>.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class EhCacheBasedUserCache implements UserCache, InitializingBean,
+    DisposableBean {
+    //~ Static fields/initializers =============================================
+
+    private static final Log logger = LogFactory.getLog(EhCacheBasedUserCache.class);
+    private static final String CACHE_NAME = "ehCacheBasedUserCache";
+
+    //~ Instance fields ========================================================
+
+    private Cache cache;
+    private CacheManager manager;
+    private int minutesToIdle = 5;
+
+    //~ Methods ================================================================
+
+    public void setMinutesToIdle(int minutesToIdle) {
+        this.minutesToIdle = minutesToIdle;
+    }
+
+    /**
+     * Specifies how many minutes an entry will remain in the cache from when
+     * it was last accessed. This is effectively the session duration.
+     * 
+     * <P>
+     * Defaults to 5 minutes.
+     * </p>
+     *
+     * @return Returns the minutes an element remains in the cache
+     */
+    public int getMinutesToIdle() {
+        return minutesToIdle;
+    }
+
+    public User getUserFromCache(String username) {
+        Element element = null;
+
+        try {
+            element = cache.get(username);
+        } catch (CacheException cacheException) {
+            throw new DataRetrievalFailureException("Cache failure: "
+                + cacheException.getMessage());
+        }
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Cache hit: " + (element != null) + "; username: "
+                + username);
+        }
+
+        if (element == null) {
+            return null;
+        } else {
+            return (User) element.getValue();
+        }
+    }
+
+    public void afterPropertiesSet() throws Exception {
+        if (CacheManager.getInstance().cacheExists(CACHE_NAME)) {
+            CacheManager.getInstance().removeCache(CACHE_NAME);
+        }
+
+        manager = CacheManager.create();
+
+        // Cache name, max memory, overflowToDisk, eternal, timeToLive, timeToIdle
+        cache = new Cache(CACHE_NAME, Integer.MAX_VALUE, false, false,
+                minutesToIdle * 60, minutesToIdle * 60);
+        manager.addCache(cache);
+    }
+
+    public void destroy() throws Exception {
+        manager.removeCache(CACHE_NAME);
+    }
+
+    public void putUserInCache(User user) {
+        Element element = new Element(user.getUsername(), user);
+
+        if (logger.isDebugEnabled()) {
+            logger.debug("Cache put: " + element.getKey());
+        }
+
+        cache.put(element);
+    }
+
+    public void removeUserFromCache(User user) {
+        if (logger.isDebugEnabled()) {
+            logger.debug("Cache remove: " + user.getUsername());
+        }
+
+        this.removeUserFromCache(user.getUsername());
+    }
+
+    public void removeUserFromCache(String username) {
+        cache.remove(username);
+    }
+}

+ 36 - 0
core/src/main/java/org/acegisecurity/providers/dao/cache/NullUserCache.java

@@ -0,0 +1,36 @@
+/* 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.providers.dao.cache;
+
+import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserCache;
+
+
+/**
+ * Does not perform any caching.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class NullUserCache implements UserCache {
+    //~ Methods ================================================================
+
+    public User getUserFromCache(String username) {
+        return null;
+    }
+
+    public void putUserInCache(User user) {}
+}

+ 5 - 0
core/src/main/java/org/acegisecurity/providers/dao/cache/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Caches <code>User</code> objects for the <code>DaoAuthenticationProvider</code>.
+</body>
+</html>

+ 0 - 1
core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml

@@ -36,7 +36,6 @@
 	<!-- Authentication provider that queries our data access object  -->
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
-		<property name="key"><value>my_password</value></property>
 	</bean>
 
 	<!-- The authentication manager that iterates through our only authentication provider -->

+ 39 - 109
core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java

@@ -25,13 +25,16 @@ import net.sf.acegisecurity.GrantedAuthority;
 import net.sf.acegisecurity.GrantedAuthorityImpl;
 import net.sf.acegisecurity.providers.TestingAuthenticationToken;
 import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache;
+import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
 import net.sf.acegisecurity.providers.dao.salt.SystemWideSaltSource;
 import net.sf.acegisecurity.providers.encoding.ShaPasswordEncoder;
 
 import org.springframework.dao.DataAccessException;
 import org.springframework.dao.DataRetrievalFailureException;
 
-import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
 
 
 /**
@@ -56,8 +59,8 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "KOala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
+        provider.setUserCache(new MockUserCache());
 
         try {
             provider.authenticate(token);
@@ -72,8 +75,8 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "opal");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserPeter());
+        provider.setUserCache(new MockUserCache());
 
         try {
             provider.authenticate(token);
@@ -88,8 +91,8 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoSimulateBackendError());
+        provider.setUserCache(new MockUserCache());
 
         try {
             provider.authenticate(token);
@@ -104,8 +107,8 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "INVALID_PASSWORD");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
+        provider.setUserCache(new MockUserCache());
 
         try {
             provider.authenticate(token);
@@ -120,8 +123,8 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
+        provider.setUserCache(new MockUserCache());
 
         try {
             provider.authenticate(token);
@@ -136,8 +139,8 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
+        provider.setUserCache(new MockUserCache());
 
         try {
             provider.authenticate(token);
@@ -152,55 +155,21 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
+        provider.setUserCache(new MockUserCache());
 
         Authentication result = provider.authenticate(token);
 
-        if (!(result instanceof DaoAuthenticationToken)) {
-            fail("Should have returned instance of DaoAuthenticationToken");
+        if (!(result instanceof UsernamePasswordAuthenticationToken)) {
+            fail(
+                "Should have returned instance of UsernamePasswordAuthenticationToken");
         }
 
-        DaoAuthenticationToken castResult = (DaoAuthenticationToken) result;
+        UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
         assertEquals("marissa", castResult.getPrincipal());
         assertEquals("koala", castResult.getCredentials());
         assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
         assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
-        assertEquals(provider.getKey().hashCode(), castResult.getKeyHash());
-    }
-
-    public void testAuthenticatesThenAcceptsCreatedTokenAutomatically() {
-        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa",
-                "koala");
-
-        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
-        provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
-
-        Authentication result = provider.authenticate(token);
-
-        if (!(result instanceof DaoAuthenticationToken)) {
-            fail("Should have returned instance of DaoAuthenticationToken");
-        }
-
-        DaoAuthenticationToken castResult = (DaoAuthenticationToken) result;
-        assertEquals("marissa", castResult.getPrincipal());
-        assertEquals(provider.getKey().hashCode(), castResult.getKeyHash());
-        assertTrue(castResult.getExpires().after(new Date()));
-
-        // Now try to re-authenticate
-        // Set provider to null, so we get a NullPointerException if it tries to re-authenticate
-        provider.setAuthenticationDao(null);
-
-        Authentication secondResult = provider.authenticate(result);
-
-        if (!(secondResult instanceof DaoAuthenticationToken)) {
-            fail("Should have returned instance of DaoAuthenticationToken");
-        }
-
-        // Should still have the same expiry time as original
-        assertEquals(castResult.getExpires(),
-            ((DaoAuthenticationToken) secondResult).getExpires());
     }
 
     public void testAuthenticatesWhenASaltIsUsed() {
@@ -211,77 +180,22 @@ public class DaoAuthenticationProviderTests extends TestCase {
         salt.setSystemWideSalt("SYSTEM_SALT_VALUE");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissaWithSalt());
         provider.setSaltSource(salt);
+        provider.setUserCache(new MockUserCache());
 
         Authentication result = provider.authenticate(token);
 
-        if (!(result instanceof DaoAuthenticationToken)) {
+        if (!(result instanceof UsernamePasswordAuthenticationToken)) {
             fail(
-                "Should have returned instance of DaoPasswordAuthenticationToken");
+                "Should have returned instance of UsernamePasswordAuthenticationToken");
         }
 
-        DaoAuthenticationToken castResult = (DaoAuthenticationToken) result;
+        UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
         assertEquals("marissa", castResult.getPrincipal());
         assertEquals("koala{SYSTEM_SALT_VALUE}", castResult.getCredentials());
         assertEquals("ROLE_ONE", castResult.getAuthorities()[0].getAuthority());
         assertEquals("ROLE_TWO", castResult.getAuthorities()[1].getAuthority());
-        assertEquals(provider.getKey().hashCode(), castResult.getKeyHash());
-    }
-
-    public void testDaoAuthenticationTokensThatHaveExpiredAreRefreshed()
-        throws Exception {
-        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken("marissa",
-                "koala");
-
-        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
-        provider.setRefreshTokenInterval(0); // never cache
-        provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
-
-        Authentication result = provider.authenticate(token);
-
-        if (!(result instanceof DaoAuthenticationToken)) {
-            fail("Should have returned instance of DaoAuthenticationToken");
-        }
-
-        DaoAuthenticationToken castResult = (DaoAuthenticationToken) result;
-        assertEquals("marissa", castResult.getPrincipal());
-        assertEquals(provider.getKey().hashCode(), castResult.getKeyHash());
-        Thread.sleep(1000);
-        assertTrue(castResult.getExpires().before(new Date())); // already expired
-
-        // Now try to re-authenticate
-        Authentication secondResult = provider.authenticate(result);
-
-        if (!(secondResult instanceof DaoAuthenticationToken)) {
-            fail("Should have returned instance of DaoAuthenticationToken");
-        }
-
-        // Should still have a later expiry time than original
-        assertTrue(castResult.getExpires().before(((DaoAuthenticationToken) secondResult)
-                .getExpires()));
-    }
-
-    public void testDaoAuthenticationTokensWithWrongKeyAreRejected()
-        throws Exception {
-        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("x");
-        provider.setRefreshTokenInterval(0); // never cache
-        provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
-
-        DaoAuthenticationToken token = new DaoAuthenticationToken("key",
-                new Date(), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        try {
-            provider.authenticate(token);
-            fail("Should have thrown BadCredentialsException");
-        } catch (BadCredentialsException expected) {
-            assertTrue(true);
-        }
     }
 
     public void testGettersSetters() {
@@ -293,12 +207,15 @@ public class DaoAuthenticationProviderTests extends TestCase {
         provider.setSaltSource(new SystemWideSaltSource());
         assertEquals(SystemWideSaltSource.class,
             provider.getSaltSource().getClass());
+
+        provider.setUserCache(new EhCacheBasedUserCache());
+        assertEquals(EhCacheBasedUserCache.class,
+            provider.getUserCache().getClass());
     }
 
     public void testStartupFailsIfNoAuthenticationDao()
         throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
-        provider.setKey("xxx");
 
         try {
             provider.afterPropertiesSet();
@@ -308,9 +225,11 @@ public class DaoAuthenticationProviderTests extends TestCase {
         }
     }
 
-    public void testStartupFailsIfNoKeySet() throws Exception {
+    public void testStartupFailsIfNoUserCacheSet() throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
+        assertEquals(NullUserCache.class, provider.getUserCache().getClass());
+        provider.setUserCache(null);
 
         try {
             provider.afterPropertiesSet();
@@ -323,8 +242,8 @@ public class DaoAuthenticationProviderTests extends TestCase {
     public void testStartupSuccess() throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
         AuthenticationDao dao = new MockAuthenticationDaoUserMarissa();
-        provider.setKey("x");
         provider.setAuthenticationDao(dao);
+        provider.setUserCache(new MockUserCache());
         assertEquals(dao, provider.getAuthenticationDao());
         provider.afterPropertiesSet();
         assertTrue(true);
@@ -334,7 +253,6 @@ public class DaoAuthenticationProviderTests extends TestCase {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
         assertTrue(provider.supports(UsernamePasswordAuthenticationToken.class));
         assertTrue(!provider.supports(TestingAuthenticationToken.class));
-        assertTrue(provider.supports(DaoAuthenticationToken.class));
     }
 
     //~ Inner Classes ==========================================================
@@ -390,4 +308,16 @@ public class DaoAuthenticationProviderTests extends TestCase {
             }
         }
     }
+
+    private class MockUserCache implements UserCache {
+        private Map cache = new HashMap();
+
+        public User getUserFromCache(String username) {
+            return (User) cache.get(username);
+        }
+
+        public void putUserInCache(User user) {
+            cache.put(user.getUsername(), user);
+        }
+    }
 }

+ 0 - 226
core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationTokenTests.java

@@ -1,226 +0,0 @@
-/* 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.providers.dao;
-
-import junit.framework.TestCase;
-
-import net.sf.acegisecurity.GrantedAuthority;
-import net.sf.acegisecurity.GrantedAuthorityImpl;
-import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
-
-import java.util.Date;
-
-
-/**
- * Tests {@link DaoAuthenticationToken}.
- *
- * @author Ben Alex
- * @version $Id$
- */
-public class DaoAuthenticationTokenTests extends TestCase {
-    //~ Constructors ===========================================================
-
-    public DaoAuthenticationTokenTests() {
-        super();
-    }
-
-    public DaoAuthenticationTokenTests(String arg0) {
-        super(arg0);
-    }
-
-    //~ Methods ================================================================
-
-    public final void setUp() throws Exception {
-        super.setUp();
-    }
-
-    public static void main(String[] args) {
-        junit.textui.TestRunner.run(DaoAuthenticationTokenTests.class);
-    }
-
-    public void testConstructorRejectsNulls() {
-        try {
-            new DaoAuthenticationToken(null, new Date(), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            new DaoAuthenticationToken("key", null, "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            new DaoAuthenticationToken("key", new Date(), null, "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            new DaoAuthenticationToken("key", new Date(), "Test", null,
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            new DaoAuthenticationToken("key", new Date(), "Test", "Password",
-                null);
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-
-        try {
-            new DaoAuthenticationToken("key", new Date(), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), null});
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-    }
-
-    public void testEqualsWhenEqual() {
-        Date date = new Date();
-
-        DaoAuthenticationToken token1 = new DaoAuthenticationToken("key", date,
-                "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        DaoAuthenticationToken token2 = new DaoAuthenticationToken("key", date,
-                "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        assertEquals(token1, token2);
-    }
-
-    public void testGetters() {
-        Date date = new Date();
-        DaoAuthenticationToken token = new DaoAuthenticationToken("key", date,
-                "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-        assertEquals("key".hashCode(), token.getKeyHash());
-        assertEquals("Test", token.getPrincipal());
-        assertEquals("Password", token.getCredentials());
-        assertEquals("ROLE_ONE", token.getAuthorities()[0].getAuthority());
-        assertEquals("ROLE_TWO", token.getAuthorities()[1].getAuthority());
-        assertEquals(date, token.getExpires());
-    }
-
-    public void testNoArgConstructor() {
-        try {
-            new DaoAuthenticationToken();
-            fail("Should have thrown IllegalArgumentException");
-        } catch (IllegalArgumentException expected) {
-            assertTrue(true);
-        }
-    }
-
-    public void testNotEqualsDueToAbstractParentEqualsCheck() {
-        Date date = new Date();
-
-        DaoAuthenticationToken token1 = new DaoAuthenticationToken("key", date,
-                "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        DaoAuthenticationToken token2 = new DaoAuthenticationToken("key", date,
-                "DIFFERENT_PRINCIPAL", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testNotEqualsDueToDifferentAuthenticationClass() {
-        DaoAuthenticationToken token1 = new DaoAuthenticationToken("key",
-                new Date(), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        UsernamePasswordAuthenticationToken token2 = new UsernamePasswordAuthenticationToken("Test",
-                "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-        token2.setAuthenticated(true);
-
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testNotEqualsDueToDifferentExpiresDate() {
-        DaoAuthenticationToken token1 = new DaoAuthenticationToken("key",
-                new Date(50000), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        DaoAuthenticationToken token2 = new DaoAuthenticationToken("key",
-                new Date(60000), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testNotEqualsDueToKey() {
-        Date date = new Date();
-
-        DaoAuthenticationToken token1 = new DaoAuthenticationToken("key", date,
-                "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        DaoAuthenticationToken token2 = new DaoAuthenticationToken("DIFFERENT_KEY",
-                date, "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-
-        assertTrue(!token1.equals(token2));
-    }
-
-    public void testSetAuthenticatedIgnored() {
-        DaoAuthenticationToken token = new DaoAuthenticationToken("key",
-                new Date(), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-        assertTrue(token.isAuthenticated());
-        token.setAuthenticated(false); // ignored
-        assertTrue(token.isAuthenticated());
-    }
-
-    public void testToString() {
-        DaoAuthenticationToken token = new DaoAuthenticationToken("key",
-                new Date(), "Test", "Password",
-                new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
-                        "ROLE_TWO")});
-        String result = token.toString();
-        assertTrue(result.lastIndexOf("Expires:") != -1);
-    }
-}

+ 83 - 0
core/src/test/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCacheTests.java

@@ -0,0 +1,83 @@
+/* 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.providers.dao.cache;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.GrantedAuthority;
+import net.sf.acegisecurity.GrantedAuthorityImpl;
+import net.sf.acegisecurity.providers.dao.User;
+
+
+/**
+ * Tests {@link EhCacheBasedUserCache}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class EhCacheBasedUserCacheTests extends TestCase {
+    //~ Constructors ===========================================================
+
+    public EhCacheBasedUserCacheTests() {
+        super();
+    }
+
+    public EhCacheBasedUserCacheTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(EhCacheBasedUserCacheTests.class);
+    }
+
+    public void testCacheOperation() throws Exception {
+        EhCacheBasedUserCache cache = new EhCacheBasedUserCache();
+        cache.afterPropertiesSet();
+
+        // Check it gets stored in the cache
+        cache.putUserInCache(getUser());
+        assertEquals(getUser().getPassword(),
+            cache.getUserFromCache(getUser().getUsername()).getPassword());
+
+        // Check it gets removed from the cache
+        cache.removeUserFromCache(getUser());
+        assertNull(cache.getUserFromCache(getUser().getUsername()));
+
+        // Check it doesn't return values for null or unknown users
+        assertNull(cache.getUserFromCache(null));
+        assertNull(cache.getUserFromCache("UNKNOWN_USER"));
+
+        cache.destroy();
+    }
+
+    public void testGettersSetters() {
+        EhCacheBasedUserCache cache = new EhCacheBasedUserCache();
+        cache.setMinutesToIdle(15);
+        assertEquals(15, cache.getMinutesToIdle());
+    }
+
+    private User getUser() {
+        return new User("john", "password", true,
+            new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
+                    "ROLE_TWO")});
+    }
+}

+ 63 - 0
core/src/test/java/org/acegisecurity/providers/dao/cache/NullUserCacheTests.java

@@ -0,0 +1,63 @@
+/* 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.providers.dao.cache;
+
+import junit.framework.TestCase;
+
+import net.sf.acegisecurity.GrantedAuthority;
+import net.sf.acegisecurity.GrantedAuthorityImpl;
+import net.sf.acegisecurity.providers.dao.User;
+
+
+/**
+ * Tests {@link NullUserCache}.
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public class NullUserCacheTests extends TestCase {
+    //~ Constructors ===========================================================
+
+    public NullUserCacheTests() {
+        super();
+    }
+
+    public NullUserCacheTests(String arg0) {
+        super(arg0);
+    }
+
+    //~ Methods ================================================================
+
+    public final void setUp() throws Exception {
+        super.setUp();
+    }
+
+    public static void main(String[] args) {
+        junit.textui.TestRunner.run(NullUserCacheTests.class);
+    }
+
+    public void testCacheOperation() throws Exception {
+        NullUserCache cache = new NullUserCache();
+        cache.putUserInCache(getUser());
+        assertNull(cache.getUserFromCache(null));
+    }
+
+    private User getUser() {
+        return new User("john", "password", true,
+            new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
+                    "ROLE_TWO")});
+    }
+}

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

@@ -36,7 +36,6 @@
 	<!-- Authentication provider that queries our data access object  -->
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
-		<property name="key"><value>my_password</value></property>
 	</bean>
 
 	<!-- The authentication manager that iterates through our only authentication provider -->

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

@@ -892,7 +892,6 @@
 
         <para><programlisting>&lt;bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider"&gt;
   &lt;property name="authenticationDao"&gt;&lt;ref bean="inMemoryDaoImpl"/&gt;&lt;/property&gt;
-  &lt;property name="key"&gt;&lt;value&gt;my_password&lt;/value&gt;&lt;/property&gt;
   &lt;property name="refreshTokenInterval"&gt;&lt;value&gt;60000&lt;/value&gt;&lt;/property&gt;
   &lt;property name="saltSource"&gt;&lt;ref bean="saltSource"/&gt;&lt;/property&gt;
   &lt;property name="passwordEncoder"&gt;&lt;ref bean="passwordEncoder"/&gt;&lt;/property&gt;
@@ -910,23 +909,30 @@
         <literal>SaltSource</literal> implementations are also provided:
         <literal>SystemWideSaltSource</literal> which encodes all passwords
         with the same salt, and <literal>ReflectionSaltSource</literal>, which
-        inspects a given property of the returned User object to obtain the
-        salt. Please refer to the JavaDocs for further details on these
-        optional features.</para>
-
-        <para>The <literal>key</literal> property permits the
-        <literal>DaoAuthenticationProvider</literal> to build a
-        <literal>DaoAuthenticationToken</literal> that represents the
-        successful authentication request. This allows the
-        <literal>DaoAuthenticationProvider</literal> to avoid repeated lookups
-        of the backend authentication repository. For a presented
-        <literal>DaoAuthenticationToken</literal> to be accepted as valid, it
-        needs to both present the expected key (to prove it was created by the
-        <literal>DaoAuthenticationProvider</literal>) and that is has not
-        expired. <literal>DaoAuthenticationToken</literal>s by default expire
-        60 seconds after they have been created, although this can be set to
-        any other millisecond value via the
-        <literal>refreshTokenInterval</literal> property.</para>
+        inspects a given property of the returned <literal>User</literal>
+        object to obtain the salt. Please refer to the JavaDocs for further
+        details on these optional features.</para>
+
+        <para>In addition to the properties above, the
+        <literal>DaoAuthenticationProvider</literal> supports optional caching
+        of <literal>User</literal> objects. The <literal>UserCache</literal>
+        interface enables the <literal>DaoAuthenticationProvider</literal> to
+        place a <literal>User</literal> object into the cache, and retrieve it
+        from the cache upon subsequent authentication attempts for the same
+        username. By default the <literal>DaoAuthenticationProvider</literal>
+        uses the <literal>NullUserCache</literal>, which performs no caching.
+        A usable caching implementation is also provided,
+        <literal>EhCacheBasedUserCache</literal>, which is configured as
+        follows:</para>
+
+        <para><programlisting>&lt;bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider"&gt;
+  &lt;property name="authenticationDao"&gt;&lt;ref bean="authenticationDao"/&gt;&lt;/property&gt;
+  &lt;property name="userCache"&gt;&lt;ref bean="userCache"/&gt;&lt;/property&gt;
+&lt;/bean&gt;
+
+&lt;bean id="userCache" class="net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache"&gt;
+  &lt;property name="minutesToIdle"&gt;&lt;value&gt;5&lt;/value&gt;&lt;/property&gt;
+&lt;/bean&gt;</programlisting></para>
 
         <para>For a class to be able to provide the
         <literal>DaoAuthenticationProvider</literal> with access to an

+ 2 - 0
samples/contacts/build.xml

@@ -152,6 +152,8 @@
 			<lib dir="${lib.dir}/j2ee" includes="jstl.jar"/>
 			<lib dir="${lib.dir}/caucho" includes="*.jar"/>
 			<lib dir="${lib.dir}/jakarta-commons" includes="commons-codec.jar"/>
+			<lib dir="${lib.dir}/jakarta-commons" includes="commons-collections.jar"/>
+			<lib dir="${lib.dir}/ehcache" includes="*.jar"/>
 			<lib dir="${dist.lib.dir}" includes="acegi-security-taglib.jar"/>
 			<lib dir="${dist.lib.dir}" includes="acegi-security.jar"/>
 		</war>

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

@@ -48,7 +48,6 @@
 	
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
-		<property name="key"><value>my_password</value></property>
 	</bean>
 
 	<bean id="basicProcessingFilter" class="net.sf.acegisecurity.ui.basicauth.BasicProcessingFilter">

+ 5 - 1
samples/contacts/etc/filter/applicationContext.xml

@@ -43,7 +43,11 @@
 	
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="inMemoryDaoImpl"/></property>
-		<property name="key"><value>my_password</value></property>
+     	<property name="userCache"><ref bean="userCache"/></property>
+	</bean>
+	
+	<bean id="userCache" class="net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
+		<property name="minutesToIdle"><value>5</value></property>
 	</bean>
 
 	<!-- Automatically receives AuthenticationEvent messages from DaoAuthenticationProvider -->

+ 5 - 2
samples/quick-start/war-root/WEB-INF/applicationContext.xml

@@ -42,11 +42,14 @@
 	</bean>
 
 	<!-- =================== SECURITY BEANS YOU WILL RARELY (IF EVER) CHANGE ================== -->
-	<!-- However, it is a good idea to change each <property name="key">'s to a new random value -->
 	
 	<bean id="daoAuthenticationProvider" class="net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider">
      	<property name="authenticationDao"><ref bean="authenticationDao"/></property>
-		<property name="key"><value>my_password</value></property>
+     	<property name="userCache"><ref bean="userCache"/></property>
+	</bean>
+	
+	<bean id="userCache" class="net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
+		<property name="minutesToIdle"><value>5</value></property>
 	</bean>
 
 	<bean id="authenticationManager" class="net.sf.acegisecurity.providers.ProviderManager">