From e0d57de330288f2ad638fc922c45d39418960bea Mon Sep 17 00:00:00 2001
From: Ben Alex
Date: Fri, 23 Apr 2004 05:01:57 +0000
Subject: [PATCH] Add DaoAuthenticationProvider caching support.
---
.../cas/applicationContext-invalid.xml | 1 +
.../adapters/cas/applicationContext-valid.xml | 1 +
changelog.txt | 2 +
.../dao/DaoAuthenticationProvider.java | 86 ++++++-
.../providers/dao/DaoAuthenticationToken.java | 148 ++++++++++++
.../adapters/adaptertest-valid.xml | 1 +
.../dao/DaoAuthenticationProviderTests.java | 129 ++++++++++-
.../dao/DaoAuthenticationTokenTests.java | 217 ++++++++++++++++++
.../ui/basicauth/filtertest-valid.xml | 1 +
.../contacts/etc/ca/applicationContext.xml | 1 +
.../etc/filter/applicationContext.xml | 1 +
upgrade-04-05.txt | 6 +
12 files changed, 577 insertions(+), 17 deletions(-)
create mode 100644 core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationToken.java
create mode 100644 core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationTokenTests.java
diff --git a/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-invalid.xml b/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-invalid.xml
index 872d1f5977..f48ec3281c 100644
--- a/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-invalid.xml
+++ b/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-invalid.xml
@@ -33,6 +33,7 @@
+ my_password
diff --git a/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-valid.xml b/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-valid.xml
index 33132386f3..66c709eebb 100644
--- a/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-valid.xml
+++ b/adapters/cas/src/test/java/org/acegisecurity/adapters/cas/applicationContext-valid.xml
@@ -33,6 +33,7 @@
+ my_password
diff --git a/changelog.txt b/changelog.txt
index 468a722f2d..3a7448d617 100644
--- a/changelog.txt
+++ b/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 tags
* Established acegisecurity-developer mailing list
diff --git a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java
index 36b58f1aba..c172870672 100644
--- a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java
+++ b/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.
*
+ *
+ *
+ * Upon successful validation, a DaoAuthenticationToken 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 AuthenticationProvider will need to be made. Once the
+ * token has expired, the relevant AuthenticationProvider 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
+ * UsernamePasswordAuthenticationToken. This avoids complications
+ * if the user changes their password during the session.
+ *
*
* @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
+ * DaoAuthenticationToken will remain valid for. Whilstever
+ * the token is valid, the DaoAuthenticationProvider 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. null
* is a valid value, meaning the DaoAuthenticationProvider
@@ -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) {
diff --git a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationToken.java b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationToken.java
new file mode 100644
index 0000000000..51bc393f5e
--- /dev/null
+++ b/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 Authentication.
+ *
+ * @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 null 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 true).
+ *
+ * @param isAuthenticated ignored
+ */
+ public void setAuthenticated(boolean isAuthenticated) {
+ // ignored
+ }
+
+ /**
+ * Always returns true.
+ *
+ * @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;
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml b/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml
index 38a23ee9ca..7f0c51c608 100644
--- a/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml
+++ b/core/src/test/java/org/acegisecurity/adapters/adaptertest-valid.xml
@@ -36,6 +36,7 @@
+ my_password
diff --git a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java
index 9986dbf8e5..2e007dd371 100644
--- a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java
+++ b/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();
diff --git a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationTokenTests.java b/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationTokenTests.java
new file mode 100644
index 0000000000..f9eac4951d
--- /dev/null
+++ b/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());
+ }
+}
diff --git a/core/src/test/java/org/acegisecurity/ui/basicauth/filtertest-valid.xml b/core/src/test/java/org/acegisecurity/ui/basicauth/filtertest-valid.xml
index 08c20c8af0..b9bd6b67bc 100644
--- a/core/src/test/java/org/acegisecurity/ui/basicauth/filtertest-valid.xml
+++ b/core/src/test/java/org/acegisecurity/ui/basicauth/filtertest-valid.xml
@@ -36,6 +36,7 @@
+ my_password
diff --git a/samples/contacts/etc/ca/applicationContext.xml b/samples/contacts/etc/ca/applicationContext.xml
index 46802b2516..48906619eb 100644
--- a/samples/contacts/etc/ca/applicationContext.xml
+++ b/samples/contacts/etc/ca/applicationContext.xml
@@ -48,6 +48,7 @@
+ my_password
diff --git a/samples/contacts/etc/filter/applicationContext.xml b/samples/contacts/etc/filter/applicationContext.xml
index 08209c45a9..ecc2831420 100644
--- a/samples/contacts/etc/filter/applicationContext.xml
+++ b/samples/contacts/etc/filter/applicationContext.xml
@@ -43,6 +43,7 @@
+ my_password
diff --git a/upgrade-04-05.txt b/upgrade-04-05.txt
index b8f3a4228b..f22eddbb7f 100644
--- a/upgrade-04-05.txt
+++ b/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.