Browse Source

Refactor User to an interface.

Ben Alex 21 years ago
parent
commit
6314aa4efa
32 changed files with 248 additions and 124 deletions
  1. 2 0
      changelog.txt
  2. 16 13
      core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java
  3. 2 2
      core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java
  4. 9 8
      core/src/main/java/org/acegisecurity/providers/dao/UserCache.java
  5. 70 0
      core/src/main/java/org/acegisecurity/providers/dao/UserDetails.java
  6. 5 5
      core/src/main/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCache.java
  7. 3 3
      core/src/main/java/org/acegisecurity/providers/dao/cache/NullUserCache.java
  8. 4 3
      core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationEvent.java
  9. 2 2
      core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailureDisabledEvent.java
  10. 2 2
      core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailurePasswordEvent.java
  11. 3 2
      core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationSuccessEvent.java
  12. 2 1
      core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java
  13. 2 1
      core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java
  14. 12 4
      core/src/main/java/org/acegisecurity/userdetails/User.java
  15. 3 3
      core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java
  16. 4 3
      core/src/main/java/org/acegisecurity/userdetails/jdbc/JdbcDaoImpl.java
  17. 2 2
      core/src/main/java/org/acegisecurity/userdetails/memory/InMemoryDaoImpl.java
  18. 2 1
      core/src/main/java/org/acegisecurity/userdetails/memory/UserMap.java
  19. 2 1
      core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java
  20. 3 2
      core/src/test/java/org/acegisecurity/providers/cas/populator/DaoCasAuthoritiesPopulatorTests.java
  21. 6 6
      core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java
  22. 7 7
      core/src/test/java/org/acegisecurity/providers/dao/UserTests.java
  23. 5 5
      core/src/test/java/org/acegisecurity/providers/dao/jdbc/JdbcDaoTests.java
  24. 5 4
      core/src/test/java/org/acegisecurity/providers/dao/memory/UserMapTests.java
  25. 3 2
      core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java
  26. 5 5
      core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java
  27. 35 25
      docs/reference/src/index.xml
  28. 3 3
      samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java
  29. 3 3
      samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java
  30. 3 3
      samples/contacts/src/main/java/sample/contact/SecureIndexController.java
  31. 3 3
      samples/contacts/src/main/java/sample/contact/WebContactAddController.java
  32. 20 0
      upgrade-05-06.txt

+ 2 - 0
changelog.txt

@@ -2,7 +2,9 @@ Changes in version 0.6 (2004-xx-xx)
 -----------------------------------
 
 * Added feature so DaoAuthenticationProvider returns User in Authentication
+* Refactored User to UserDetails interface
 * Fixed Linux compatibility issues (directory case sensitivity etc)
+* Fixed AbstractProcessingFilter to handle servlet spec container differences
 * Documentation improvements
 
 Changes in version 0.51 (2004-06-06)

+ 16 - 13
core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java

@@ -52,21 +52,22 @@ import org.springframework.dao.DataAccessException;
  * Upon successful validation, a
  * <code>UsernamePasswordAuthenticationToken</code> will be created and
  * returned to the caller. The token will include as its principal either a
- * <code>String</code> representation of the username, or the {@link User}
- * that was returned from the authentication repository. Using
+ * <code>String</code> representation of the username, or the {@link
+ * UserDetails} that was returned from the authentication repository. Using
  * <code>String</code> is appropriate if a container adapter is being used, as
  * it expects <code>String</code> representations of the username. Using
- * <code>User</code> is appropriate if you require access to additional
+ * <code>UserDetails</code> is appropriate if you require access to additional
  * properties of the authenticated user, such as email addresses,
  * human-friendly names etc. As container adapters are not recommended to be
- * used, and <code>User</code> provides additional flexibility, by default a
- * <code>User</code> is returned. To override this default, set the {@link
- * #setForcePrincipalAsString} to <code>true</code>.
+ * used, and <code>UserDetails</code> implementations provide additional
+ * flexibility, by default a <code>UserDetails</code> is returned. To override
+ * this default, set the {@link #setForcePrincipalAsString} to
+ * <code>true</code>.
  * </p>
  * 
  * <P>
- * Caching is handled via the <code>User</code> object being placed in the
- * {@link UserCache}. This ensures that subsequent requests with the same
+ * Caching is handled via the <code>UserDetails</code> object being placed in
+ * the {@link UserCache}. This ensures 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
@@ -174,12 +175,13 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
         // Determine username
         String username = authentication.getPrincipal().toString();
 
-        if (authentication.getPrincipal() instanceof User) {
-            username = ((User) authentication.getPrincipal()).getUsername();
+        if (authentication.getPrincipal() instanceof UserDetails) {
+            username = ((UserDetails) authentication.getPrincipal())
+                .getUsername();
         }
 
         boolean cacheWasUsed = true;
-        User user = this.userCache.getUserFromCache(username);
+        UserDetails user = this.userCache.getUserFromCache(username);
 
         if (user == null) {
             cacheWasUsed = false;
@@ -244,7 +246,8 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
         }
     }
 
-    protected boolean isPasswordCorrect(Authentication authentication, User user) {
+    protected boolean isPasswordCorrect(Authentication authentication,
+        UserDetails user) {
         Object salt = null;
 
         if (this.saltSource != null) {
@@ -255,7 +258,7 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
             authentication.getCredentials().toString(), salt);
     }
 
-    private User getUserFromBackend(String username) {
+    private UserDetails getUserFromBackend(String username) {
         try {
             return this.authenticationDao.loadUserByUsername(username);
         } catch (UsernameNotFoundException notFound) {

+ 2 - 2
core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java

@@ -29,7 +29,7 @@ public interface SaltSource {
      *
      * @param user from the <code>AuthenticationDao</code>
      *
-     * @return the salt to use for this <code>USer</code>
+     * @return the salt to use for this <code>UserDetails</code>
      */
-    public Object getSalt(User user);
+    public Object getSalt(UserDetails user);
 }

+ 9 - 8
core/src/main/java/org/acegisecurity/providers/dao/UserCache.java

@@ -33,21 +33,22 @@ public interface UserCache {
     //~ Methods ================================================================
 
     /**
-     * Obtains a {@link User} from the cache.
+     * Obtains a {@link UserDetails} 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
+     * @return the populated <code>UserDetails</code> or <code>null</code> if
+     *         the user could not be found or if the cache entry has expired
      */
-    public User getUserFromCache(String username);
+    public UserDetails 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>.
+     * Places a {@link UserDetails} in the cache. The <code>username</code> is
+     * the key used to subsequently retrieve the <code>UserDetails</code>.
      *
-     * @param user the fully populated <code>User</code> to place in the cache
+     * @param user the fully populated <code>UserDetails</code> to place in the
+     *        cache
      */
-    public void putUserInCache(User user);
+    public void putUserInCache(UserDetails user);
 }

+ 70 - 0
core/src/main/java/org/acegisecurity/providers/dao/UserDetails.java

@@ -0,0 +1,70 @@
+/* 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 java.io.Serializable;
+
+
+/**
+ * Provides core user information required by the package.
+ * 
+ * <P>
+ * Concrete implementations must take particular care to ensure the non-null
+ * contract detailed for each method is enforced. See {@link User} for a
+ * reference implementation (which you might like to extend).
+ * </p>
+ *
+ * @author Ben Alex
+ * @version $Id$
+ */
+public interface UserDetails extends Serializable {
+    //~ Methods ================================================================
+
+    /**
+     * Returns the authorities granted to the user. Cannot return
+     * <code>null</code>.
+     *
+     * @return the authorities (never <code>null</code>)
+     */
+    public GrantedAuthority[] getAuthorities();
+
+    /**
+     * Indicates whether the user is enabled or disabled. A disabled user
+     * cannot be authenticated.
+     *
+     * @return <code>true</code> if the user is enabled, <code>false</code>
+     *         otherwise
+     */
+    public boolean isEnabled();
+
+    /**
+     * Returns the password used to authenticate the user. Cannot return
+     * <code>null</code>.
+     *
+     * @return the password (never <code>null</code>)
+     */
+    public String getPassword();
+
+    /**
+     * Returns the username used to authenticate the user. Cannot return
+     * <code>null</code>.
+     *
+     * @return the username (never <code>null</code>)
+     */
+    public String getUsername();
+}

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

@@ -15,8 +15,8 @@
 
 package net.sf.acegisecurity.providers.dao.cache;
 
-import net.sf.acegisecurity.providers.dao.User;
 import net.sf.acegisecurity.providers.dao.UserCache;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import net.sf.ehcache.Cache;
 import net.sf.ehcache.CacheException;
@@ -72,7 +72,7 @@ public class EhCacheBasedUserCache implements UserCache, InitializingBean,
         return minutesToIdle;
     }
 
-    public User getUserFromCache(String username) {
+    public UserDetails getUserFromCache(String username) {
         Element element = null;
 
         try {
@@ -90,7 +90,7 @@ public class EhCacheBasedUserCache implements UserCache, InitializingBean,
         if (element == null) {
             return null;
         } else {
-            return (User) element.getValue();
+            return (UserDetails) element.getValue();
         }
     }
 
@@ -111,7 +111,7 @@ public class EhCacheBasedUserCache implements UserCache, InitializingBean,
         manager.removeCache(CACHE_NAME);
     }
 
-    public void putUserInCache(User user) {
+    public void putUserInCache(UserDetails user) {
         Element element = new Element(user.getUsername(), user);
 
         if (logger.isDebugEnabled()) {
@@ -121,7 +121,7 @@ public class EhCacheBasedUserCache implements UserCache, InitializingBean,
         cache.put(element);
     }
 
-    public void removeUserFromCache(User user) {
+    public void removeUserFromCache(UserDetails user) {
         if (logger.isDebugEnabled()) {
             logger.debug("Cache remove: " + user.getUsername());
         }

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

@@ -15,8 +15,8 @@
 
 package net.sf.acegisecurity.providers.dao.cache;
 
-import net.sf.acegisecurity.providers.dao.User;
 import net.sf.acegisecurity.providers.dao.UserCache;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 
 /**
@@ -28,9 +28,9 @@ import net.sf.acegisecurity.providers.dao.UserCache;
 public class NullUserCache implements UserCache {
     //~ Methods ================================================================
 
-    public User getUserFromCache(String username) {
+    public UserDetails getUserFromCache(String username) {
         return null;
     }
 
-    public void putUserInCache(User user) {}
+    public void putUserInCache(UserDetails user) {}
 }

+ 4 - 3
core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationEvent.java

@@ -17,6 +17,7 @@ package net.sf.acegisecurity.providers.dao.event;
 
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import org.springframework.context.ApplicationEvent;
 
@@ -44,11 +45,11 @@ import org.springframework.context.ApplicationEvent;
 public abstract class AuthenticationEvent extends ApplicationEvent {
     //~ Instance fields ========================================================
 
-    private User user;
+    private UserDetails user;
 
     //~ Constructors ===========================================================
 
-    public AuthenticationEvent(Authentication authentication, User user) {
+    public AuthenticationEvent(Authentication authentication, UserDetails user) {
         super(authentication);
 
         // No need to check authentication isn't null, as done by super
@@ -77,7 +78,7 @@ public abstract class AuthenticationEvent extends ApplicationEvent {
      *
      * @return the user
      */
-    public User getUser() {
+    public UserDetails getUser() {
         return user;
     }
 }

+ 2 - 2
core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailureDisabledEvent.java

@@ -16,7 +16,7 @@
 package net.sf.acegisecurity.providers.dao.event;
 
 import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 
 /**
@@ -30,7 +30,7 @@ public class AuthenticationFailureDisabledEvent extends AuthenticationEvent {
     //~ Constructors ===========================================================
 
     public AuthenticationFailureDisabledEvent(Authentication authentication,
-        User user) {
+        UserDetails user) {
         super(authentication, user);
     }
 }

+ 2 - 2
core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailurePasswordEvent.java

@@ -16,7 +16,7 @@
 package net.sf.acegisecurity.providers.dao.event;
 
 import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 
 /**
@@ -30,7 +30,7 @@ public class AuthenticationFailurePasswordEvent extends AuthenticationEvent {
     //~ Constructors ===========================================================
 
     public AuthenticationFailurePasswordEvent(Authentication authentication,
-        User user) {
+        UserDetails user) {
         super(authentication, user);
     }
 }

+ 3 - 2
core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationSuccessEvent.java

@@ -16,7 +16,7 @@
 package net.sf.acegisecurity.providers.dao.event;
 
 import net.sf.acegisecurity.Authentication;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 
 /**
@@ -28,7 +28,8 @@ import net.sf.acegisecurity.providers.dao.User;
 public class AuthenticationSuccessEvent extends AuthenticationEvent {
     //~ Constructors ===========================================================
 
-    public AuthenticationSuccessEvent(Authentication authentication, User user) {
+    public AuthenticationSuccessEvent(Authentication authentication,
+        UserDetails user) {
         super(authentication, user);
     }
 }

+ 2 - 1
core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java

@@ -18,6 +18,7 @@ package net.sf.acegisecurity.providers.dao.salt;
 import net.sf.acegisecurity.AuthenticationServiceException;
 import net.sf.acegisecurity.providers.dao.SaltSource;
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import org.springframework.beans.factory.InitializingBean;
 
@@ -60,7 +61,7 @@ public class ReflectionSaltSource implements SaltSource, InitializingBean {
      *
      * @throws AuthenticationServiceException if reflection fails
      */
-    public Object getSalt(User user) {
+    public Object getSalt(UserDetails user) {
         try {
             Method reflectionMethod = user.getClass().getMethod(this.userPropertyToUse,
                     null);

+ 2 - 1
core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java

@@ -17,6 +17,7 @@ package net.sf.acegisecurity.providers.dao.salt;
 
 import net.sf.acegisecurity.providers.dao.SaltSource;
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import org.springframework.beans.factory.InitializingBean;
 
@@ -41,7 +42,7 @@ public class SystemWideSaltSource implements SaltSource, InitializingBean {
 
     //~ Methods ================================================================
 
-    public Object getSalt(User user) {
+    public Object getSalt(UserDetails user) {
         return this.systemWideSalt;
     }
 

+ 12 - 4
core/src/main/java/org/acegisecurity/userdetails/User.java

@@ -17,16 +17,20 @@ package net.sf.acegisecurity.providers.dao;
 
 import net.sf.acegisecurity.GrantedAuthority;
 
-import java.io.Serializable;
-
 
 /**
  * Models core user information retieved by an {@link AuthenticationDao}.
+ * 
+ * <P>
+ * Implemented with value object semantics (immutable after construction, like
+ * a <code>String</code>). Developers may use this class directly, subclass
+ * it, or write their own {@link UserDetails} implementation from scratch.
+ * </p>
  *
  * @author Ben Alex
  * @version $Id$
  */
-public class User implements Serializable {
+public class User implements UserDetails {
     //~ Instance fields ========================================================
 
     private String password;
@@ -37,7 +41,7 @@ public class User implements Serializable {
     //~ Constructors ===========================================================
 
     /**
-     * Construct the <code>User</code> with the details required by  {@link
+     * Construct the <code>User</code> with the details required by {@link
      * DaoAuthenticationProvider}.
      *
      * @param username the username presented to the
@@ -96,4 +100,8 @@ public class User implements Serializable {
     public String getUsername() {
         return username;
     }
+
+    public String toString() {
+        return username;
+    }
 }

+ 3 - 3
core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java

@@ -37,8 +37,8 @@ public interface AuthenticationDao {
      * Locates the user based on the username. In the actual implementation,
      * the search may possibly be case insensitive, or case insensitive
      * depending on how the implementaion instance is configured. In this
-     * case, the User object that comes back may have a username that is of a
-     * different case than what was actually requested..
+     * case, the <code>UserDetails</code> object that comes back may have a
+     * username that is of a different case than what was actually requested..
      *
      * @param username the username presented to the {@link
      *        DaoAuthenticationProvider}
@@ -50,6 +50,6 @@ public interface AuthenticationDao {
      * @throws DataAccessException if user could not be found for a
      *         repository-specific reason
      */
-    public User loadUserByUsername(String username)
+    public UserDetails loadUserByUsername(String username)
         throws UsernameNotFoundException, DataAccessException;
 }

+ 4 - 3
core/src/main/java/org/acegisecurity/userdetails/jdbc/JdbcDaoImpl.java

@@ -19,6 +19,7 @@ import net.sf.acegisecurity.GrantedAuthority;
 import net.sf.acegisecurity.GrantedAuthorityImpl;
 import net.sf.acegisecurity.providers.dao.AuthenticationDao;
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
 
 import org.apache.commons.logging.Log;
@@ -161,7 +162,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements AuthenticationDao {
         return usersByUsernameQuery;
     }
 
-    public User loadUserByUsername(String username)
+    public UserDetails loadUserByUsername(String username)
         throws UsernameNotFoundException, DataAccessException {
         List users = usersByUsernameMapping.execute(username);
 
@@ -169,7 +170,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements AuthenticationDao {
             throw new UsernameNotFoundException("User not found");
         }
 
-        User user = (User) users.get(0); // contains no GrantedAuthority[]
+        UserDetails user = (UserDetails) users.get(0); // contains no GrantedAuthority[]
 
         List dbAuths = authoritiesByUsernameMapping.execute(user.getUsername());
 
@@ -234,7 +235,7 @@ public class JdbcDaoImpl extends JdbcDaoSupport implements AuthenticationDao {
             String username = rs.getString(1);
             String password = rs.getString(2);
             boolean enabled = rs.getBoolean(3);
-            User user = new User(username, password, enabled,
+            UserDetails user = new User(username, password, enabled,
                     new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
 
             return user;

+ 2 - 2
core/src/main/java/org/acegisecurity/userdetails/memory/InMemoryDaoImpl.java

@@ -16,7 +16,7 @@
 package net.sf.acegisecurity.providers.dao.memory;
 
 import net.sf.acegisecurity.providers.dao.AuthenticationDao;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
 
 import org.springframework.beans.factory.InitializingBean;
@@ -52,7 +52,7 @@ public class InMemoryDaoImpl implements AuthenticationDao, InitializingBean {
         }
     }
 
-    public User loadUserByUsername(String username)
+    public UserDetails loadUserByUsername(String username)
         throws UsernameNotFoundException, DataAccessException {
         return userMap.getUser(username);
     }

+ 2 - 1
core/src/main/java/org/acegisecurity/userdetails/memory/UserMap.java

@@ -16,6 +16,7 @@
 package net.sf.acegisecurity.providers.dao.memory;
 
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
 
 import org.apache.commons.logging.Log;
@@ -80,7 +81,7 @@ public class UserMap {
      *
      * @throws IllegalArgumentException if a null User was passed
      */
-    public void addUser(User user) throws IllegalArgumentException {
+    public void addUser(UserDetails user) throws IllegalArgumentException {
         if (user == null) {
             throw new IllegalArgumentException("Must be a valid User");
         }

+ 2 - 1
core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java

@@ -16,6 +16,7 @@
 package net.sf.acegisecurity.providers.dao.memory;
 
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import org.springframework.beans.propertyeditors.PropertiesEditor;
 
@@ -89,7 +90,7 @@ public class UserMapEditor extends PropertyEditorSupport {
 
                 // Make a user object, assuming the properties were properly provided
                 if (attr != null) {
-                    User user = new User(username, attr.getPassword(),
+                    UserDetails user = new User(username, attr.getPassword(),
                             attr.isEnabled(), attr.getAuthorities());
                     userMap.addUser(user);
                 }

+ 3 - 2
core/src/test/java/org/acegisecurity/providers/cas/populator/DaoCasAuthoritiesPopulatorTests.java

@@ -21,6 +21,7 @@ import net.sf.acegisecurity.GrantedAuthority;
 import net.sf.acegisecurity.GrantedAuthorityImpl;
 import net.sf.acegisecurity.providers.dao.AuthenticationDao;
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
 
 import org.springframework.dao.DataAccessException;
@@ -121,7 +122,7 @@ public class DaoCasAuthoritiesPopulatorTests extends TestCase {
             return 0;
         }
 
-        public User loadUserByUsername(String username)
+        public UserDetails loadUserByUsername(String username)
             throws UsernameNotFoundException, DataAccessException {
             throw new DataRetrievalFailureException(
                 "This mock simulator is designed to fail");
@@ -133,7 +134,7 @@ public class DaoCasAuthoritiesPopulatorTests extends TestCase {
             return 0;
         }
 
-        public User loadUserByUsername(String username)
+        public UserDetails loadUserByUsername(String username)
             throws UsernameNotFoundException, DataAccessException {
             if ("marissa".equals(username)) {
                 return new User("marissa", "koala", true,

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

@@ -261,7 +261,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
 
     private class MockAuthenticationDaoSimulateBackendError
         implements AuthenticationDao {
-        public User loadUserByUsername(String username)
+        public UserDetails loadUserByUsername(String username)
             throws UsernameNotFoundException, DataAccessException {
             throw new DataRetrievalFailureException(
                 "This mock simulator is designed to fail");
@@ -269,7 +269,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
     }
 
     private class MockAuthenticationDaoUserMarissa implements AuthenticationDao {
-        public User loadUserByUsername(String username)
+        public UserDetails loadUserByUsername(String username)
             throws UsernameNotFoundException, DataAccessException {
             if ("marissa".equals(username)) {
                 return new User("marissa", "koala", true,
@@ -284,7 +284,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
 
     private class MockAuthenticationDaoUserMarissaWithSalt
         implements AuthenticationDao {
-        public User loadUserByUsername(String username)
+        public UserDetails loadUserByUsername(String username)
             throws UsernameNotFoundException, DataAccessException {
             if ("marissa".equals(username)) {
                 return new User("marissa", "koala{SYSTEM_SALT_VALUE}", true,
@@ -298,7 +298,7 @@ public class DaoAuthenticationProviderTests extends TestCase {
     }
 
     private class MockAuthenticationDaoUserPeter implements AuthenticationDao {
-        public User loadUserByUsername(String username)
+        public UserDetails loadUserByUsername(String username)
             throws UsernameNotFoundException, DataAccessException {
             if ("peter".equals(username)) {
                 return new User("peter", "opal", false,
@@ -314,11 +314,11 @@ public class DaoAuthenticationProviderTests extends TestCase {
     private class MockUserCache implements UserCache {
         private Map cache = new HashMap();
 
-        public User getUserFromCache(String username) {
+        public UserDetails getUserFromCache(String username) {
             return (User) cache.get(username);
         }
 
-        public void putUserInCache(User user) {
+        public void putUserInCache(UserDetails user) {
             cache.put(user.getUsername(), user);
         }
     }

+ 7 - 7
core/src/test/java/org/acegisecurity/providers/dao/UserTests.java

@@ -59,7 +59,7 @@ public class UserTests extends TestCase {
 
     public void testNullValuesRejected() throws Exception {
         try {
-            User user = new User(null, "koala", true,
+            UserDetails user = new User(null, "koala", true,
                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                             "ROLE_TWO")});
             fail("Should have thrown IllegalArgumentException");
@@ -68,7 +68,7 @@ public class UserTests extends TestCase {
         }
 
         try {
-            User user = new User("marissa", null, true,
+            UserDetails user = new User("marissa", null, true,
                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                             "ROLE_TWO")});
             fail("Should have thrown IllegalArgumentException");
@@ -77,14 +77,14 @@ public class UserTests extends TestCase {
         }
 
         try {
-            User user = new User("marissa", "koala", true, null);
+            UserDetails user = new User("marissa", "koala", true, null);
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
             assertTrue(true);
         }
 
         try {
-            User user = new User("marissa", "koala", true,
+            UserDetails user = new User("marissa", "koala", true,
                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), null});
             fail("Should have thrown IllegalArgumentException");
         } catch (IllegalArgumentException expected) {
@@ -95,7 +95,7 @@ public class UserTests extends TestCase {
     public void testNullWithinGrantedAuthorityElementIsRejected()
         throws Exception {
         try {
-            User user = new User(null, "koala", true,
+            UserDetails user = new User(null, "koala", true,
                     new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                             "ROLE_TWO"), null, new GrantedAuthorityImpl(
                             "ROLE_THREE")});
@@ -106,7 +106,7 @@ public class UserTests extends TestCase {
     }
 
     public void testUserGettersSetter() throws Exception {
-        User user = new User("marissa", "koala", true,
+        UserDetails user = new User("marissa", "koala", true,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                         "ROLE_TWO")});
         assertEquals("marissa", user.getUsername());
@@ -119,7 +119,7 @@ public class UserTests extends TestCase {
     }
 
     public void testUserIsEnabled() throws Exception {
-        User user = new User("marissa", "koala", false,
+        UserDetails user = new User("marissa", "koala", false,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                         "ROLE_TWO")});
         assertTrue(!user.isEnabled());

+ 5 - 5
core/src/test/java/org/acegisecurity/providers/dao/jdbc/JdbcDaoTests.java

@@ -17,7 +17,7 @@ package net.sf.acegisecurity.providers.dao.jdbc;
 
 import junit.framework.TestCase;
 
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
 
 import org.springframework.jdbc.datasource.DriverManagerDataSource;
@@ -56,7 +56,7 @@ public class JdbcDaoTests extends TestCase {
 
     public void testCheckDaoAccessUserSuccess() throws Exception {
         JdbcDaoImpl dao = makePopulatedJdbcDao();
-        User user = dao.loadUserByUsername("marissa");
+        UserDetails user = dao.loadUserByUsername("marissa");
         assertEquals("marissa", user.getUsername());
         assertEquals("koala", user.getPassword());
         assertTrue(user.isEnabled());
@@ -68,7 +68,7 @@ public class JdbcDaoTests extends TestCase {
     public void testCheckDaoOnlyReturnsGrantedAuthoritiesGrantedToUser()
         throws Exception {
         JdbcDaoImpl dao = makePopulatedJdbcDao();
-        User user = dao.loadUserByUsername("scott");
+        UserDetails user = dao.loadUserByUsername("scott");
         assertEquals("ROLE_TELLER", user.getAuthorities()[0].getAuthority());
         assertEquals(1, user.getAuthorities().length);
     }
@@ -76,7 +76,7 @@ public class JdbcDaoTests extends TestCase {
     public void testCheckDaoReturnsCorrectDisabledProperty()
         throws Exception {
         JdbcDaoImpl dao = makePopulatedJdbcDao();
-        User user = dao.loadUserByUsername("peter");
+        UserDetails user = dao.loadUserByUsername("peter");
         assertTrue(!user.isEnabled());
     }
 
@@ -128,7 +128,7 @@ public class JdbcDaoTests extends TestCase {
         JdbcDaoImpl dao = makePopulatedJdbcDaoWithRolePrefix();
         assertEquals("ARBITRARY_PREFIX_", dao.getRolePrefix());
 
-        User user = dao.loadUserByUsername("marissa");
+        UserDetails user = dao.loadUserByUsername("marissa");
         assertEquals("marissa", user.getUsername());
         assertEquals("ARBITRARY_PREFIX_ROLE_TELLER",
             user.getAuthorities()[0].getAuthority());

+ 5 - 4
core/src/test/java/org/acegisecurity/providers/dao/memory/UserMapTests.java

@@ -20,6 +20,7 @@ import junit.framework.TestCase;
 import net.sf.acegisecurity.GrantedAuthority;
 import net.sf.acegisecurity.GrantedAuthorityImpl;
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
 
 
@@ -51,13 +52,13 @@ public class UserMapTests extends TestCase {
     }
 
     public void testAddAndRetrieveUser() {
-        User marissa = new User("marissa", "koala", true,
+        UserDetails marissa = new User("marissa", "koala", true,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                         "ROLE_TWO")});
-        User scott = new User("scott", "wombat", true,
+        UserDetails scott = new User("scott", "wombat", true,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                         "ROLE_THREE")});
-        User peter = new User("peter", "opal", true,
+        UserDetails peter = new User("peter", "opal", true,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                         "ROLE_FOUR")});
         UserMap map = new UserMap();
@@ -84,7 +85,7 @@ public class UserMapTests extends TestCase {
     }
 
     public void testUnknownUserIsNotRetrieved() {
-        User marissa = new User("marissa", "koala", true,
+        UserDetails marissa = new User("marissa", "koala", true,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_ONE"), new GrantedAuthorityImpl(
                         "ROLE_TWO")});
         UserMap map = new UserMap();

+ 3 - 2
core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java

@@ -21,6 +21,7 @@ import net.sf.acegisecurity.AuthenticationServiceException;
 import net.sf.acegisecurity.GrantedAuthority;
 import net.sf.acegisecurity.GrantedAuthorityImpl;
 import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 
 /**
@@ -66,7 +67,7 @@ public class ReflectionSaltSourceTests extends TestCase {
         ReflectionSaltSource saltSource = new ReflectionSaltSource();
         saltSource.setUserPropertyToUse("getDoesNotExist");
 
-        User user = new User("scott", "wombat", true,
+        UserDetails user = new User("scott", "wombat", true,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
 
         try {
@@ -88,7 +89,7 @@ public class ReflectionSaltSourceTests extends TestCase {
         saltSource.setUserPropertyToUse("getUsername");
         saltSource.afterPropertiesSet();
 
-        User user = new User("scott", "wombat", true,
+        UserDetails user = new User("scott", "wombat", true,
                 new GrantedAuthority[] {new GrantedAuthorityImpl("HOLDER")});
         assertEquals("scott", saltSource.getSalt(user));
     }

+ 5 - 5
core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java

@@ -24,7 +24,7 @@ import net.sf.acegisecurity.MockFilterConfig;
 import net.sf.acegisecurity.MockHttpServletRequest;
 import net.sf.acegisecurity.MockHttpServletResponse;
 import net.sf.acegisecurity.MockHttpSession;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.ui.webapp.HttpSessionIntegrationFilter;
 
 import org.apache.commons.codec.binary.Base64;
@@ -200,8 +200,8 @@ public class BasicProcessingFilterTests extends TestCase {
 
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
         assertEquals("marissa",
-            ((User) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal())
-             .getUsername());
+            ((UserDetails) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY))
+            .getPrincipal()).getUsername());
     }
 
     public void testOtherAuthorizationSchemeIsIgnored()
@@ -292,8 +292,8 @@ public class BasicProcessingFilterTests extends TestCase {
 
         assertTrue(request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY) != null);
         assertEquals("marissa",
-                ((User) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY)).getPrincipal())
-                 .getUsername());
+            ((UserDetails) ((Authentication) request.getSession().getAttribute(HttpSessionIntegrationFilter.ACEGI_SECURITY_AUTHENTICATION_KEY))
+            .getPrincipal()).getUsername());
 
         // NOW PERFORM FAILED AUTHENTICATION
         // Setup our HTTP request

+ 35 - 25
docs/reference/src/index.xml

@@ -901,15 +901,16 @@
         <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 <literal>User</literal>
-        object to obtain the salt. Please refer to the JavaDocs for further
-        details on these optional features.</para>
+        inspects a given property of the returned
+        <literal>UserDetails</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
+        of <literal>UserDetails</literal> objects. The
+        <literal>UserCache</literal> interface enables the
+        <literal>DaoAuthenticationProvider</literal> to place a
+        <literal>UserDetails</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.
@@ -931,11 +932,19 @@
         authentication repository, it must implement the
         <literal>AuthenticationDao</literal> interface:</para>
 
-        <para><programlisting>public User loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException;</programlisting></para>
+        <para><programlisting>public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException;</programlisting></para>
 
-        <para>The <literal>User</literal> object holds basic information such
-        as the username, password, granted authorities and whether the user is
-        enabled or disabled.</para>
+        <para>The <literal>UserDetails</literal> is an interface that provides
+        getters that guarantee non-null provision of basic authentication
+        information such as the username, password, granted authorities and
+        whether the user is enabled or disabled. A concrete implementation,
+        <literal>User</literal>, is also provided. Acegi Security users will
+        need to decide when writing their <literal>AuthenticationDao</literal>
+        what type of <literal>UserDetails</literal> to return. In most cases
+        <literal>User</literal> will be used directly or subclassed, although
+        special circumstances (such as object relational mappers) may require
+        users to write their own <literal>UserDetails</literal> implementation
+        from scratch.</para>
 
         <para>Given <literal>AuthenticationDao</literal> is so simple to
         implement, it should be easy for users to retrieve authentication
@@ -953,14 +962,14 @@
         <literal>Authentication</literal> object which in turn has its
         <literal>principal</literal> property set. The principal will be
         either a <literal>String</literal> (which is essentially the username)
-        or a <literal>User</literal> object (which was looked up from the
-        <literal>AuthenticationDao</literal>). By default the
-        <literal>User</literal> is returned, as this enables applications to
-        subclass <literal>User</literal> and add extra properties potentially
-        of use in applications, such as the user's full name, email address
-        etc. If using container adapters, or if your applications were written
-        to operate with <literal>String</literal>s (as was the case for
-        releases prior to Acegi Security 0.6), you should set the
+        or a <literal>UserDetails</literal> object (which was looked up from
+        the <literal>AuthenticationDao</literal>). By default the
+        <literal>UserDetails</literal> is returned, as this enables
+        applications to add extra properties potentially of use in
+        applications, such as the user's full name, email address etc. If
+        using container adapters, or if your applications were written to
+        operate with <literal>String</literal>s (as was the case for releases
+        prior to Acegi Security 0.6), you should set the
         <literal>DaoAuthenticationProvider.forcePrincipalAsString</literal>
         property to <literal>true</literal> in your application
         context.</para>
@@ -998,8 +1007,8 @@
 
         <para>Each event contains two objects: the
         <literal>Authentication</literal> object that represented the
-        authentication request, and the <literal>User</literal> object that
-        was found in response to the authentication request. The
+        authentication request, and the <literal>UserDetails</literal> object
+        that was found in response to the authentication request. The
         <literal>Authentication</literal> interface provides a
         <literal>getDetails()</literal> method which often includes
         information that event consumers may find useful (eg the TCP/IP
@@ -2455,11 +2464,12 @@ $CATALINA_HOME/bin/startup.sh</programlisting></para>
             contained in the <literal>TicketResponse</literal>. Acegi Security
             includes a <literal>DaoCasAuthoritiesPopulator</literal> which
             simply uses the <literal>AuthenticationDao</literal>
-            infrastructure to find the <literal>User</literal> and their
-            associated <literal>GrantedAuthority</literal>s. Note that the
-            password and enabled/disabled status of <literal>User</literal>s
-            returned by the <literal>AuthenticationDao</literal> are ignored,
-            as the CAS server is responsible for authentication decisions.
+            infrastructure to find the <literal>UserDetails</literal> and
+            their associated <literal>GrantedAuthority</literal>s. Note that
+            the password and enabled/disabled status of
+            <literal>UserDetails</literal> returned by the
+            <literal>AuthenticationDao</literal> are ignored, as the CAS
+            server is responsible for authentication decisions.
             <literal>DaoCasAuthoritiesPopulator</literal> is only concerned
             with retrieving the <literal>GrantedAuthority</literal>s.</para>
           </listitem>

+ 3 - 3
samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java

@@ -19,7 +19,7 @@ import net.sf.acegisecurity.AccessDeniedException;
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.context.ContextHolder;
 import net.sf.acegisecurity.context.SecureContext;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import org.springframework.beans.factory.InitializingBean;
 
@@ -91,8 +91,8 @@ public class ContactManagerFacade implements ContactManager, InitializingBean {
 
         String username = auth.getPrincipal().toString();
 
-        if (auth.getPrincipal() instanceof User) {
-            username = ((User) auth.getPrincipal()).getUsername();
+        if (auth.getPrincipal() instanceof UserDetails) {
+            username = ((UserDetails) auth.getPrincipal()).getUsername();
         }
 
         if (username.equals(result.getOwner())) {

+ 3 - 3
samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java

@@ -18,7 +18,7 @@ package sample.contact;
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.ConfigAttribute;
 import net.sf.acegisecurity.ConfigAttributeDefinition;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 import net.sf.acegisecurity.vote.AccessDecisionVoter;
 
 import org.aopalliance.intercept.MethodInvocation;
@@ -99,8 +99,8 @@ public class ContactSecurityVoter implements AccessDecisionVoter {
                 if (passedOwner != null) {
                     String username = authentication.getPrincipal().toString();
 
-                    if (authentication.getPrincipal() instanceof User) {
-                        username = ((User) authentication.getPrincipal())
+                    if (authentication.getPrincipal() instanceof UserDetails) {
+                        username = ((UserDetails) authentication.getPrincipal())
                             .getUsername();
                     }
 

+ 3 - 3
samples/contacts/src/main/java/sample/contact/SecureIndexController.java

@@ -20,7 +20,7 @@ import net.sf.acegisecurity.AuthenticationCredentialsNotFoundException;
 import net.sf.acegisecurity.GrantedAuthority;
 import net.sf.acegisecurity.context.ContextHolder;
 import net.sf.acegisecurity.context.SecureContext;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import org.springframework.beans.factory.InitializingBean;
 
@@ -80,8 +80,8 @@ public class SecureIndexController implements Controller, InitializingBean {
         Authentication auth = secureContext.getAuthentication();
         String username = auth.getPrincipal().toString();
 
-        if (auth.getPrincipal() instanceof User) {
-            username = ((User) auth.getPrincipal()).getUsername();
+        if (auth.getPrincipal() instanceof UserDetails) {
+            username = ((UserDetails) auth.getPrincipal()).getUsername();
         }
 
         boolean supervisor = false;

+ 3 - 3
samples/contacts/src/main/java/sample/contact/WebContactAddController.java

@@ -18,7 +18,7 @@ package sample.contact;
 import net.sf.acegisecurity.Authentication;
 import net.sf.acegisecurity.context.ContextHolder;
 import net.sf.acegisecurity.context.SecureContext;
-import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserDetails;
 
 import org.springframework.web.servlet.ModelAndView;
 import org.springframework.web.servlet.mvc.SimpleFormController;
@@ -61,8 +61,8 @@ public class WebContactAddController extends SimpleFormController {
             .getAuthentication();
         String owner = auth.getPrincipal().toString();
 
-        if (auth.getPrincipal() instanceof User) {
-            owner = ((User) auth.getPrincipal()).getUsername();
+        if (auth.getPrincipal() instanceof UserDetails) {
+            owner = ((UserDetails) auth.getPrincipal()).getUsername();
         }
 
         Contact contact = new Contact(contactManager.getNextId(), name, email,

+ 20 - 0
upgrade-05-06.txt

@@ -23,5 +23,25 @@ applications:
       username = ((User) authentication.getPrincipal()).getUsername();
     }
 
+- The signature of AuthenticationDaos have changed. In concrete
+  implementations, modify the User to UserDetails, as shown below:
+
+    public User loadUserByUsername(String username)
+        throws UsernameNotFoundException, DataAccessException {
+
+    to:
+  
+    public UserDetails loadUserByUsername(String username)
+        throws UsernameNotFoundException, DataAccessException {
+
+  Existing concrete implementations would be returning User, which implements
+  UserDetails, so no further code changes should be required.
+
+- Similar signature changes (User -> UserDetails) are also required to any
+  custom implementations of UserCache and SaltSource.
+
+- Any custom event listeners relying on AuthenticationEvent should note a
+  UserDetails is now provided in the AuthenticationEvent (not a User).
+
 
 $Id$