Bläddra i källkod

Add DaoAuthenticationProvider caching support.

Ben Alex 21 år sedan
förälder
incheckning
e0d57de330

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

@@ -33,6 +33,7 @@
 	
 	<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">

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

@@ -33,6 +33,7 @@
 	
 	<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">

+ 2 - 0
changelog.txt

@@ -3,6 +3,7 @@ Changes in version 0.5 (2004-xx-xx)
 
 * Added single sign on support via Yale Central Authentication Service (CAS)
 * Added full support for HTTP Basic Authentication
+* Added caching for DaoAuthenticationProvider successful authentications
 * Added Burlap and Hessian remoting to Contacts sample application
 * Added pluggable password encoders including plaintext, SHA and MD5
 * Added pluggable salt sources to enhance security of hashed passwords
@@ -14,6 +15,7 @@ Changes in version 0.5 (2004-xx-xx)
 * Added Apache Ant path syntax support to SecurityEnforcementFilter
 * Updated JAR to Spring 1.0.1
 * Refactored filters to use Spring application context lifecycle support
+* Improved constructor detection of nulls in User and other key objects
 * Fixed FilterInvocation.getRequestUrl() to also include getPathInfo()
 * Fixed Contacts sample application <A></A> tags
 * Established acegisecurity-developer mailing list

+ 77 - 9
core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java

@@ -29,6 +29,8 @@ import org.springframework.beans.factory.InitializingBean;
 
 import org.springframework.dao.DataAccessException;
 
+import java.util.Date;
+
 
 /**
  * An {@link AuthenticationProvider} implementation that retrieves user details
@@ -39,6 +41,21 @@ import org.springframework.dao.DataAccessException;
  * 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.
+ * </p>
  *
  * @author Ben Alex
  * @version $Id$
@@ -50,6 +67,8 @@ 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
 
     //~ Methods ================================================================
 
@@ -61,6 +80,14 @@ 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
@@ -76,6 +103,22 @@ 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>
@@ -98,10 +141,29 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
             throw new IllegalArgumentException(
                 "An Authentication DAO must be set");
         }
+
+        if ((this.key == null) || "".equals(key)) {
+            throw new IllegalArgumentException("A key 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;
 
         try {
@@ -114,23 +176,29 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
                 .getMessage(), repositoryProblem);
         }
 
-        Object salt = null;
+        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 (this.saltSource != null) {
+                salt = this.saltSource.getSalt(user);
+            }
 
-        if (!passwordEncoder.isPasswordValid(user.getPassword(),
-                authentication.getCredentials().toString(), salt)) {
-            throw new BadCredentialsException("Bad credentials presented");
+            if (!passwordEncoder.isPasswordValid(user.getPassword(),
+                    authentication.getCredentials().toString(), salt)) {
+                throw new BadCredentialsException("Bad credentials presented");
+            }
         }
 
         if (!user.isEnabled()) {
             throw new DisabledException("User is disabled");
         }
 
-        return new UsernamePasswordAuthenticationToken(user.getUsername(),
-            authentication.getCredentials().toString(), user.getAuthorities());
+        Date expiry = new Date(new Date().getTime()
+                + this.getRefreshTokenInterval());
+
+        return new DaoAuthenticationToken(this.getKey(), expiry,
+            user.getUsername(), user.getPassword(), user.getAuthorities());
     }
 
     public boolean supports(Class authentication) {

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

@@ -0,0 +1,148 @@
+/* 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;
+    }
+}

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

@@ -36,6 +36,7 @@
 	<!-- 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 -->

+ 121 - 8
core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java

@@ -31,6 +31,8 @@ import net.sf.acegisecurity.providers.encoding.ShaPasswordEncoder;
 import org.springframework.dao.DataAccessException;
 import org.springframework.dao.DataRetrievalFailureException;
 
+import java.util.Date;
+
 
 /**
  * Tests {@link DaoAuthenticationProvider}.
@@ -54,6 +56,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "KOala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
 
         try {
@@ -69,6 +72,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "opal");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserPeter());
 
         try {
@@ -84,6 +88,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoSimulateBackendError());
 
         try {
@@ -99,6 +104,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "INVALID_PASSWORD");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
 
         try {
@@ -114,6 +120,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
 
         try {
@@ -129,6 +136,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
 
         try {
@@ -144,20 +152,55 @@ public class DaoAuthenticationProviderTests extends TestCase {
                 "koala");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
 
         Authentication result = provider.authenticate(token);
 
-        if (!(result instanceof UsernamePasswordAuthenticationToken)) {
-            fail(
-                "Should have returned instance of UsernamePasswordAuthenticationToken");
+        if (!(result instanceof DaoAuthenticationToken)) {
+            fail("Should have returned instance of DaoAuthenticationToken");
         }
 
-        UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
+        DaoAuthenticationToken castResult = (DaoAuthenticationToken) 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() {
@@ -168,21 +211,77 @@ public class DaoAuthenticationProviderTests extends TestCase {
         salt.setSystemWideSalt("SYSTEM_SALT_VALUE");
 
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("x");
         provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissaWithSalt());
         provider.setSaltSource(salt);
 
         Authentication result = provider.authenticate(token);
 
-        if (!(result instanceof UsernamePasswordAuthenticationToken)) {
+        if (!(result instanceof DaoAuthenticationToken)) {
             fail(
-                "Should have returned instance of UsernamePasswordAuthenticationToken");
+                "Should have returned instance of DaoPasswordAuthenticationToken");
         }
 
-        UsernamePasswordAuthenticationToken castResult = (UsernamePasswordAuthenticationToken) result;
+        DaoAuthenticationToken castResult = (DaoAuthenticationToken) result;
         assertEquals("marissa", castResult.getPrincipal());
-        assertEquals("koala", castResult.getCredentials());
+        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() {
@@ -199,6 +298,19 @@ public class DaoAuthenticationProviderTests extends TestCase {
     public void testStartupFailsIfNoAuthenticationDao()
         throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setKey("xxx");
+
+        try {
+            provider.afterPropertiesSet();
+            fail("Should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+            assertTrue(true);
+        }
+    }
+
+    public void testStartupFailsIfNoKeySet() throws Exception {
+        DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
+        provider.setAuthenticationDao(new MockAuthenticationDaoUserMarissa());
 
         try {
             provider.afterPropertiesSet();
@@ -211,6 +323,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
     public void testStartupSuccess() throws Exception {
         DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
         AuthenticationDao dao = new MockAuthenticationDaoUserMarissa();
+        provider.setKey("x");
         provider.setAuthenticationDao(dao);
         assertEquals(dao, provider.getAuthenticationDao());
         provider.afterPropertiesSet();

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

@@ -0,0 +1,217 @@
+/* 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());
+    }
+}

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

@@ -36,6 +36,7 @@
 	<!-- 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 -->

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

@@ -48,6 +48,7 @@
 	
 	<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">

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

@@ -43,6 +43,7 @@
 	
 	<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">

+ 6 - 0
upgrade-04-05.txt

@@ -24,6 +24,12 @@ applications:
   requested username. The new PlaintextPasswordEncoder offers a setter for
   ignoring the password case (defaults to require exact case matches).
 
+- DaoAuthenticationProvider now provides caching. Successful authentications
+  return DaoAuthenticationTokens. You must set the mandatory "key" property
+  on DaoAuthenticationProvider so these tokens can be validated. You may
+  also wish to change the "refreshTokenInterval" property from the default
+  of 60,000 milliseconds.
+
 - If you're using container adapters, please refer to the reference
   documentation as additional JARs are now required in your container
   classloader.