diff --git a/changelog.txt b/changelog.txt index 5b666ff215..de3a69d25b 100644 --- a/changelog.txt +++ b/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) 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 89ce4c7edf..4cc31408ae 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java +++ b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java @@ -52,21 +52,22 @@ import org.springframework.dao.DataAccessException; * Upon successful validation, a * UsernamePasswordAuthenticationToken will be created and * returned to the caller. The token will include as its principal either a - * String representation of the username, or the {@link User} - * that was returned from the authentication repository. Using + * String representation of the username, or the {@link + * UserDetails} that was returned from the authentication repository. Using * String is appropriate if a container adapter is being used, as * it expects String representations of the username. Using - * User is appropriate if you require access to additional + * UserDetails 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 User provides additional flexibility, by default a - * User is returned. To override this default, set the {@link - * #setForcePrincipalAsString} to true. + * used, and UserDetails implementations provide additional + * flexibility, by default a UserDetails is returned. To override + * this default, set the {@link #setForcePrincipalAsString} to + * true. *

* *

- * Caching is handled via the User object being placed in the - * {@link UserCache}. This ensures that subsequent requests with the same + * Caching is handled via the UserDetails 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) { diff --git a/core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java b/core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java index a9ad278364..b5a2e7f592 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java +++ b/core/src/main/java/org/acegisecurity/providers/dao/SaltSource.java @@ -29,7 +29,7 @@ public interface SaltSource { * * @param user from the AuthenticationDao * - * @return the salt to use for this USer + * @return the salt to use for this UserDetails */ - public Object getSalt(User user); + public Object getSalt(UserDetails user); } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/UserCache.java b/core/src/main/java/org/acegisecurity/providers/dao/UserCache.java index 9daa3fc2c8..7f68d2c46d 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/UserCache.java +++ b/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 User or null if the user - * could not be found or if the cache entry has expired + * @return the populated UserDetails or null 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 username is the key - * used to subsequently retrieve the User. + * Places a {@link UserDetails} in the cache. The username is + * the key used to subsequently retrieve the UserDetails. * - * @param user the fully populated User to place in the cache + * @param user the fully populated UserDetails to place in the + * cache */ - public void putUserInCache(User user); + public void putUserInCache(UserDetails user); } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/UserDetails.java b/core/src/main/java/org/acegisecurity/providers/dao/UserDetails.java new file mode 100644 index 0000000000..7fdb9296af --- /dev/null +++ b/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. + * + *

+ * 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). + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public interface UserDetails extends Serializable { + //~ Methods ================================================================ + + /** + * Returns the authorities granted to the user. Cannot return + * null. + * + * @return the authorities (never null) + */ + public GrantedAuthority[] getAuthorities(); + + /** + * Indicates whether the user is enabled or disabled. A disabled user + * cannot be authenticated. + * + * @return true if the user is enabled, false + * otherwise + */ + public boolean isEnabled(); + + /** + * Returns the password used to authenticate the user. Cannot return + * null. + * + * @return the password (never null) + */ + public String getPassword(); + + /** + * Returns the username used to authenticate the user. Cannot return + * null. + * + * @return the username (never null) + */ + public String getUsername(); +} diff --git a/core/src/main/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCache.java b/core/src/main/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCache.java index 7f248667ed..7586790e1b 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/cache/EhCacheBasedUserCache.java +++ b/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()); } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/cache/NullUserCache.java b/core/src/main/java/org/acegisecurity/providers/dao/cache/NullUserCache.java index 26357d959e..ed969adbe0 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/cache/NullUserCache.java +++ b/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) {} } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationEvent.java b/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationEvent.java index 2fd5fc28c9..173dce45f4 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationEvent.java +++ b/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; } } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailureDisabledEvent.java b/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailureDisabledEvent.java index 7fda5fa7b8..2d51fb827a 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailureDisabledEvent.java +++ b/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); } } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailurePasswordEvent.java b/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailurePasswordEvent.java index ec65c5a212..51292957a0 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationFailurePasswordEvent.java +++ b/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); } } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationSuccessEvent.java b/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationSuccessEvent.java index d379778361..7024c010f2 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/event/AuthenticationSuccessEvent.java +++ b/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); } } diff --git a/core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java b/core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java index a9a708e35b..2703c0c098 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSource.java +++ b/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); diff --git a/core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java b/core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java index c765dc292f..32b6176820 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/salt/SystemWideSaltSource.java +++ b/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; } diff --git a/core/src/main/java/org/acegisecurity/userdetails/User.java b/core/src/main/java/org/acegisecurity/userdetails/User.java index 2c46e51b52..99e911ea34 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/User.java +++ b/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}. + * + *

+ * Implemented with value object semantics (immutable after construction, like + * a String). Developers may use this class directly, subclass + * it, or write their own {@link UserDetails} implementation from scratch. + *

* * @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 User with the details required by {@link + * Construct the User 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; + } } diff --git a/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java b/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java index d0e52197a8..e0e2fde9a8 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/UserDetailsService.java +++ b/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 UserDetails 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; } diff --git a/core/src/main/java/org/acegisecurity/userdetails/jdbc/JdbcDaoImpl.java b/core/src/main/java/org/acegisecurity/userdetails/jdbc/JdbcDaoImpl.java index b078473dcb..7ad2341a19 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/jdbc/JdbcDaoImpl.java +++ b/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; diff --git a/core/src/main/java/org/acegisecurity/userdetails/memory/InMemoryDaoImpl.java b/core/src/main/java/org/acegisecurity/userdetails/memory/InMemoryDaoImpl.java index ffb5d01138..78979ecad3 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/memory/InMemoryDaoImpl.java +++ b/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); } diff --git a/core/src/main/java/org/acegisecurity/userdetails/memory/UserMap.java b/core/src/main/java/org/acegisecurity/userdetails/memory/UserMap.java index 5766db714c..9f2d1254df 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/memory/UserMap.java +++ b/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"); } diff --git a/core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java b/core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java index 65160ccb29..c5f73bce40 100644 --- a/core/src/main/java/org/acegisecurity/userdetails/memory/UserMapEditor.java +++ b/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); } diff --git a/core/src/test/java/org/acegisecurity/providers/cas/populator/DaoCasAuthoritiesPopulatorTests.java b/core/src/test/java/org/acegisecurity/providers/cas/populator/DaoCasAuthoritiesPopulatorTests.java index 71ea54160d..58e32dc41b 100644 --- a/core/src/test/java/org/acegisecurity/providers/cas/populator/DaoCasAuthoritiesPopulatorTests.java +++ b/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, 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 1e2a37fcfe..45c48cdb1e 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/DaoAuthenticationProviderTests.java +++ b/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); } } diff --git a/core/src/test/java/org/acegisecurity/providers/dao/UserTests.java b/core/src/test/java/org/acegisecurity/providers/dao/UserTests.java index c0e7650d37..c37158f020 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/UserTests.java +++ b/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()); diff --git a/core/src/test/java/org/acegisecurity/providers/dao/jdbc/JdbcDaoTests.java b/core/src/test/java/org/acegisecurity/providers/dao/jdbc/JdbcDaoTests.java index 0780193195..99d99bfdb9 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/jdbc/JdbcDaoTests.java +++ b/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()); diff --git a/core/src/test/java/org/acegisecurity/providers/dao/memory/UserMapTests.java b/core/src/test/java/org/acegisecurity/providers/dao/memory/UserMapTests.java index 2961e1de30..c430042fe7 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/memory/UserMapTests.java +++ b/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(); diff --git a/core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java b/core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java index 5db350b92a..77e1d1f5cb 100644 --- a/core/src/test/java/org/acegisecurity/providers/dao/salt/ReflectionSaltSourceTests.java +++ b/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)); } diff --git a/core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java b/core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java index 1f3c8fa451..df21c0d773 100644 --- a/core/src/test/java/org/acegisecurity/ui/basicauth/BasicProcessingFilterTests.java +++ b/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 diff --git a/docs/reference/src/index.xml b/docs/reference/src/index.xml index 0d7027008b..80c2e9577d 100644 --- a/docs/reference/src/index.xml +++ b/docs/reference/src/index.xml @@ -901,15 +901,16 @@ SaltSource implementations are also provided: SystemWideSaltSource which encodes all passwords with the same salt, and ReflectionSaltSource, which - inspects a given property of the returned User - object to obtain the salt. Please refer to the JavaDocs for further - details on these optional features. + inspects a given property of the returned + UserDetails object to obtain the salt. Please refer + to the JavaDocs for further details on these optional features. In addition to the properties above, the DaoAuthenticationProvider supports optional caching - of User objects. The UserCache - interface enables the DaoAuthenticationProvider to - place a User object into the cache, and retrieve it + of UserDetails objects. The + UserCache interface enables the + DaoAuthenticationProvider to place a + UserDetails object into the cache, and retrieve it from the cache upon subsequent authentication attempts for the same username. By default the DaoAuthenticationProvider uses the NullUserCache, which performs no caching. @@ -931,11 +932,19 @@ authentication repository, it must implement the AuthenticationDao interface: - public User loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException; + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException; - The User object holds basic information such - as the username, password, granted authorities and whether the user is - enabled or disabled. + The UserDetails 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, + User, is also provided. Acegi Security users will + need to decide when writing their AuthenticationDao + what type of UserDetails to return. In most cases + User will be used directly or subclassed, although + special circumstances (such as object relational mappers) may require + users to write their own UserDetails implementation + from scratch. Given AuthenticationDao is so simple to implement, it should be easy for users to retrieve authentication @@ -953,14 +962,14 @@ Authentication object which in turn has its principal property set. The principal will be either a String (which is essentially the username) - or a User object (which was looked up from the - AuthenticationDao). By default the - User is returned, as this enables applications to - subclass User 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 Strings (as was the case for - releases prior to Acegi Security 0.6), you should set the + or a UserDetails object (which was looked up from + the AuthenticationDao). By default the + UserDetails 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 Strings (as was the case for releases + prior to Acegi Security 0.6), you should set the DaoAuthenticationProvider.forcePrincipalAsString property to true in your application context. @@ -998,8 +1007,8 @@ Each event contains two objects: the Authentication object that represented the - authentication request, and the User object that - was found in response to the authentication request. The + authentication request, and the UserDetails object + that was found in response to the authentication request. The Authentication interface provides a getDetails() method which often includes information that event consumers may find useful (eg the TCP/IP @@ -2455,11 +2464,12 @@ $CATALINA_HOME/bin/startup.sh contained in the TicketResponse. Acegi Security includes a DaoCasAuthoritiesPopulator which simply uses the AuthenticationDao - infrastructure to find the User and their - associated GrantedAuthoritys. Note that the - password and enabled/disabled status of Users - returned by the AuthenticationDao are ignored, - as the CAS server is responsible for authentication decisions. + infrastructure to find the UserDetails and + their associated GrantedAuthoritys. Note that + the password and enabled/disabled status of + UserDetails returned by the + AuthenticationDao are ignored, as the CAS + server is responsible for authentication decisions. DaoCasAuthoritiesPopulator is only concerned with retrieving the GrantedAuthoritys. diff --git a/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java b/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java index 4cc23dd6a5..d1f260969a 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactManagerFacade.java +++ b/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())) { diff --git a/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java b/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java index 792f49697d..4b69c5efac 100644 --- a/samples/contacts/src/main/java/sample/contact/ContactSecurityVoter.java +++ b/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(); } diff --git a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java b/samples/contacts/src/main/java/sample/contact/SecureIndexController.java index ea75a074fb..441586bf98 100644 --- a/samples/contacts/src/main/java/sample/contact/SecureIndexController.java +++ b/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; diff --git a/samples/contacts/src/main/java/sample/contact/WebContactAddController.java b/samples/contacts/src/main/java/sample/contact/WebContactAddController.java index 24c7138f44..6ae84c3539 100644 --- a/samples/contacts/src/main/java/sample/contact/WebContactAddController.java +++ b/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, diff --git a/upgrade-05-06.txt b/upgrade-05-06.txt index 5a351f4edb..909797cafe 100644 --- a/upgrade-05-06.txt +++ b/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$