diff --git a/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java new file mode 100644 index 0000000000..8153fc80fc --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java @@ -0,0 +1,280 @@ +/* Copyright 2004, 2005 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.AccountExpiredException; +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.AuthenticationException; +import net.sf.acegisecurity.CredentialsExpiredException; +import net.sf.acegisecurity.DisabledException; +import net.sf.acegisecurity.LockedException; +import net.sf.acegisecurity.UserDetails; +import net.sf.acegisecurity.providers.AuthenticationProvider; +import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; +import net.sf.acegisecurity.providers.dao.cache.NullUserCache; + +import org.springframework.beans.factory.InitializingBean; + +import org.springframework.util.Assert; + + +/** + * A base {@link AuthenticationProvider} that allows subclasses to override and + * work with {@link net.sf.acegisecurity.UserDetails} objects. The class is + * designed to respond to {@link UsernamePasswordAuthenticationToken} + * authentication requests. + * + *
+ * 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
+ * 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
+ * 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 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 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
+ * confirm the most up-to-date password was used for comparison.
+ *
UserDetails for a given authentication request.
+ * Generally a subclass will at least compare the {@link
+ * Authentication#getCredentials()} with a {@link
+ * UserDetails#getPassword()}. If custom logic is needed to compare
+ * additional properties of UserDetails and/or
+ * UsernamePasswordAuthenticationToken, these should also
+ * appear in this method.
+ *
+ * @param userDetails as retrieved from the {@link #retrieveUser(String,
+ * UsernamePasswordAuthenticationToken)} or UserCache
+ * @param authentication the current request that needs to be authenticated
+ *
+ * @throws AuthenticationException AuthenticationException if the
+ * credentials could not be validated (generally a
+ * BadCredentialsException, an
+ * AuthenticationServiceException)
+ */
+ protected abstract void additionalAuthenticationChecks(
+ UserDetails userDetails,
+ UsernamePasswordAuthenticationToken authentication)
+ throws AuthenticationException;
+
+ protected void doAfterPropertiesSet() throws Exception {}
+
+ /**
+ * Allows subclasses to actually retrieve the UserDetails from
+ * an implementation-specific location, with the option of throwing an
+ * AuthenticationException immediately if the presented
+ * credentials are incorrect (this is especially useful if it is necessary
+ * to bind to a resource as the user in order to obtain or generate a
+ * UserDetails).
+ *
+ *
+ * Subclasses are not required to perform any caching, as the
+ * AbstractUserDetailsAuthenticationProvider will by default
+ * cache the UserDetails. The caching of
+ * UserDetails does present additional complexity as this
+ * means subsequent requests that rely on the cache will need to still
+ * have their credentials validated, even if the correctness of
+ * credentials was assured by subclasses adopting a binding-based strategy
+ * in this method. Accordingly it is important that subclasses either
+ * disable caching (if they want to ensure that this method is the only
+ * method that is capable of authenticating a request, as no
+ * UserDetails will ever be cached) or ensure subclasses
+ * implement {@link #additionalAuthenticationChecks(UserDetails,
+ * UsernamePasswordAuthenticationToken)} to compare the credentials of a
+ * cached UserDetails with subsequent authentication
+ * requests.
+ *
+ * Most of the time subclasses will not perform credentials inspection in + * this method, instead performing it in {@link + * #additionalAuthenticationChecks(UserDetails, + * UsernamePasswordAuthenticationToken)} so that code related to + * credentials validation need not be duplicated across two methods. + *
+ * + * @param username The username to retrieve + * @param authentication The authentication request, which subclasses + * may need to perform a binding-based retrieval of the + *UserDetails
+ *
+ * @return the user information (never null - instead an
+ * exception should the thrown)
+ *
+ * @throws AuthenticationException if the credentials could not be
+ * validated (generally a BadCredentialsException, an
+ * AuthenticationServiceException or
+ * UserNotFoundException)
+ */
+ protected abstract UserDetails retrieveUser(String username,
+ UsernamePasswordAuthenticationToken authentication)
+ throws AuthenticationException;
+
+ /**
+ * Creates a successful {@link Authentication} object.
+ *
+ * + * Protected so subclasses can override. + *
+ * + *
+ * Subclasses will usually store the original credentials the user supplied
+ * (not salted or encoded passwords) in the returned
+ * Authentication object.
+ *
DaoAuthenticationProvider for validation
+ * @param user that was loaded by the AuthenticationDao
+ *
+ * @return the successful authentication token
+ */
+ protected Authentication createSuccessAuthentication(Object principal,
+ Authentication authentication, UserDetails user) {
+ // Ensure we return the original credentials the user supplied,
+ // so subsequent attempts are successful even with encoded passwords.
+ // Also ensure we return the original getDetails(), so that future
+ // authentication events after cache expiry contain the details
+ UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
+ authentication.getCredentials(), user.getAuthorities());
+ result.setDetails((authentication.getDetails() != null)
+ ? authentication.getDetails() : null);
+
+ return result;
+ }
+}
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 a3b503f06d..132d5b3885 100644
--- a/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java
+++ b/core/src/main/java/org/acegisecurity/providers/dao/DaoAuthenticationProvider.java
@@ -15,23 +15,15 @@
package net.sf.acegisecurity.providers.dao;
-import net.sf.acegisecurity.AccountExpiredException;
-import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationException;
import net.sf.acegisecurity.AuthenticationServiceException;
import net.sf.acegisecurity.BadCredentialsException;
-import net.sf.acegisecurity.CredentialsExpiredException;
-import net.sf.acegisecurity.DisabledException;
-import net.sf.acegisecurity.LockedException;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.AuthenticationProvider;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
-import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
import net.sf.acegisecurity.providers.encoding.PasswordEncoder;
import net.sf.acegisecurity.providers.encoding.PlaintextPasswordEncoder;
-import org.springframework.beans.factory.InitializingBean;
-
import org.springframework.dao.DataAccessException;
import org.springframework.util.Assert;
@@ -40,51 +32,17 @@ import org.springframework.util.Assert;
/**
* An {@link AuthenticationProvider} implementation that retrieves user details
* from an {@link AuthenticationDao}.
- *
- *
- * This AuthenticationProvider is capable of validating {@link
- * UsernamePasswordAuthenticationToken} requests contain the correct username,
- * password and the user is not disabled.
- *
- * 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
- * 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
- * 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 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 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
- * confirm the most up-to-date password was used for comparison.
- *
DaoAuthenticationProvider throws a
* BadCredentialsException if a username is not found or the
@@ -159,158 +109,29 @@ public class DaoAuthenticationProvider implements AuthenticationProvider,
return saltSource;
}
- public void setUserCache(UserCache userCache) {
- this.userCache = userCache;
- }
-
- public UserCache getUserCache() {
- return userCache;
- }
-
- public void afterPropertiesSet() throws Exception {
- Assert.notNull(this.authenticationDao,
- "An Authentication DAO must be set");
- Assert.notNull(this.userCache, "A user cache must be set");
- }
-
- public Authentication authenticate(Authentication authentication)
+ protected void additionalAuthenticationChecks(UserDetails userDetails,
+ UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
- // Determine username
- String username = "NONE_PROVIDED";
-
- if (authentication.getPrincipal() != null) {
- username = authentication.getPrincipal().toString();
- }
-
- if (authentication.getPrincipal() instanceof UserDetails) {
- username = ((UserDetails) authentication.getPrincipal())
- .getUsername();
- }
-
- boolean cacheWasUsed = true;
- UserDetails user = this.userCache.getUserFromCache(username);
-
- if (user == null) {
- cacheWasUsed = false;
- user = getUserFromBackend(username);
- }
-
- if (!user.isAccountNonLocked()) {
- throw new LockedException("User account is locked");
- }
-
- if (!isPasswordCorrect(authentication, user)) {
- // Password incorrect, so ensure we're using most current password
- if (cacheWasUsed) {
- cacheWasUsed = false;
- user = getUserFromBackend(username);
- }
-
- if (!isPasswordCorrect(authentication, user)) {
- throw new BadCredentialsException("Bad credentials presented");
- }
- }
-
- if (!user.isEnabled()) {
- throw new DisabledException("User is disabled");
- }
-
- if (!user.isAccountNonExpired()) {
- throw new AccountExpiredException("User account has expired");
- }
-
- if (!user.isCredentialsNonExpired()) {
- throw new CredentialsExpiredException(
- "User credentials have expired");
- }
-
- if (!cacheWasUsed) {
- // Put into cache
- this.userCache.putUserInCache(user);
- }
-
- Object principalToReturn = user;
-
- if (forcePrincipalAsString) {
- principalToReturn = user.getUsername();
- }
-
- return createSuccessAuthentication(principalToReturn, authentication,
- user);
- }
-
- public boolean supports(Class authentication) {
- if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(
- authentication)) {
- return true;
- } else {
- return false;
- }
- }
-
- /**
- * Indicates whether the supplied Authentication object
- * provided appropriate credentials. This method can be called several
- * times throughout a single authentication request.
- *
- * - * Protected so subclasses can override. - *
- * - * @param authentication that was presented to the - *DaoAuthenticationProvider for validation
- * @param user that was loaded by the AuthenticationDao
- *
- * @return a boolean indicating whether the credentials were correct
- */
- protected boolean isPasswordCorrect(Authentication authentication,
- UserDetails user) {
Object salt = null;
if (this.saltSource != null) {
- salt = this.saltSource.getSalt(user);
+ salt = this.saltSource.getSalt(userDetails);
}
- return passwordEncoder.isPasswordValid(user.getPassword(),
- authentication.getCredentials().toString(), salt);
+ if (!passwordEncoder.isPasswordValid(userDetails.getPassword(),
+ authentication.getCredentials().toString(), salt)) {
+ throw new BadCredentialsException("Bad credentials");
+ }
}
- /**
- * Creates a successful {@link Authentication} object.
- *
- * - * Protected so subclasses can override. - *
- * - *
- * Subclasses will usually store the original credentials the user supplied
- * (not salted or encoded passwords) in the returned
- * Authentication object.
- *
DaoAuthenticationProvider for validation
- * @param user that was loaded by the AuthenticationDao
- *
- * @return the successful authentication token
- */
- protected Authentication createSuccessAuthentication(Object principal,
- Authentication authentication, UserDetails user) {
- // Ensure we return the original credentials the user supplied,
- // so subsequent attempts are successful even with encoded passwords.
- // Also ensure we return the original getDetails(), so that future
- // authentication events after cache expiry contain the details
- UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
- authentication.getCredentials(), user.getAuthorities());
- result.setDetails((authentication.getDetails() != null)
- ? authentication.getDetails() : null);
-
- return result;
+ protected void doAfterPropertiesSet() throws Exception {
+ Assert.notNull(this.authenticationDao,
+ "An Authentication DAO must be set");
}
- private UserDetails getUserFromBackend(String username) {
+ protected final UserDetails retrieveUser(String username,
+ UsernamePasswordAuthenticationToken authentication)
+ throws AuthenticationException {
UserDetails loadedUser;
try {
diff --git a/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java
index 2b13fe20a2..739f7a5b24 100644
--- a/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java
+++ b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/LdapPasswordAuthenticationDao.java
@@ -24,7 +24,6 @@ import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.providers.dao.PasswordAuthenticationDao;
import net.sf.acegisecurity.providers.dao.User;
/**
diff --git a/core/src/main/java/org/acegisecurity/providers/dao/PasswordAuthenticationDao.java b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/PasswordAuthenticationDao.java
similarity index 94%
rename from core/src/main/java/org/acegisecurity/providers/dao/PasswordAuthenticationDao.java
rename to sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/PasswordAuthenticationDao.java
index fc56936873..11f802dbef 100644
--- a/core/src/main/java/org/acegisecurity/providers/dao/PasswordAuthenticationDao.java
+++ b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/PasswordAuthenticationDao.java
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-package net.sf.acegisecurity.providers.dao;
+package net.sf.acegisecurity.providers.dao.ldap;
import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.UserDetails;
@@ -33,7 +33,8 @@ import org.springframework.dao.DataAccessException;
* The interface requires only one read-only method, which simplifies support
* of new data access strategies.
*
- *
+ *
+ * @deprecated instead subclass {@link net.sf.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider}
* @author Karel Miarka
*/
public interface PasswordAuthenticationDao {
diff --git a/core/src/main/java/org/acegisecurity/providers/dao/PasswordDaoAuthenticationProvider.java b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/PasswordDaoAuthenticationProvider.java
similarity index 96%
rename from core/src/main/java/org/acegisecurity/providers/dao/PasswordDaoAuthenticationProvider.java
rename to sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/PasswordDaoAuthenticationProvider.java
index 06ef995793..86158d0c76 100644
--- a/core/src/main/java/org/acegisecurity/providers/dao/PasswordDaoAuthenticationProvider.java
+++ b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/PasswordDaoAuthenticationProvider.java
@@ -13,7 +13,7 @@
* limitations under the License.
*/
-package net.sf.acegisecurity.providers.dao;
+package net.sf.acegisecurity.providers.dao.ldap;
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
@@ -25,6 +25,8 @@ import net.sf.acegisecurity.LockedException;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.AuthenticationProvider;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import net.sf.acegisecurity.providers.dao.DaoAuthenticationProvider;
+import net.sf.acegisecurity.providers.dao.UserCache;
import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
import org.springframework.beans.factory.InitializingBean;
@@ -83,6 +85,7 @@ import org.springframework.util.Assert;
* further information.
*
*
+ * @deprecated instead subclass {@link net.sf.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider}
* @author Karel Miarka
*/
public class PasswordDaoAuthenticationProvider implements AuthenticationProvider,
diff --git a/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/revised/LdapPassword2AuthenticationDao.java b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/revised/LdapPassword2AuthenticationDao.java
index cf5b836841..e9f0930bd2 100644
--- a/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/revised/LdapPassword2AuthenticationDao.java
+++ b/sandbox/src/main/java/org/acegisecurity/providers/dao/ldap/revised/LdapPassword2AuthenticationDao.java
@@ -24,7 +24,7 @@ import net.sf.acegisecurity.BadCredentialsException;
import net.sf.acegisecurity.GrantedAuthority;
import net.sf.acegisecurity.GrantedAuthorityImpl;
import net.sf.acegisecurity.UserDetails;
-import net.sf.acegisecurity.providers.dao.PasswordAuthenticationDao;
+import net.sf.acegisecurity.providers.dao.ldap.PasswordAuthenticationDao;
/**
* Re-written version of the ACEGI LDAP code,
diff --git a/core/src/test/java/org/acegisecurity/providers/dao/PasswordDaoAuthenticationProviderTests.java b/sandbox/src/test/java/org/acegisecurity/providers/dao/ldap/PasswordDaoAuthenticationProviderTests.java
similarity index 98%
rename from core/src/test/java/org/acegisecurity/providers/dao/PasswordDaoAuthenticationProviderTests.java
rename to sandbox/src/test/java/org/acegisecurity/providers/dao/ldap/PasswordDaoAuthenticationProviderTests.java
index da8aadeb3e..008fe490e5 100644
--- a/core/src/test/java/org/acegisecurity/providers/dao/PasswordDaoAuthenticationProviderTests.java
+++ b/sandbox/src/test/java/org/acegisecurity/providers/dao/ldap/PasswordDaoAuthenticationProviderTests.java
@@ -13,10 +13,12 @@
* limitations under the License.
*/
-package net.sf.acegisecurity.providers.dao;
+package net.sf.acegisecurity.providers.dao.ldap;
+
+import java.util.HashMap;
+import java.util.Map;
import junit.framework.TestCase;
-
import net.sf.acegisecurity.AccountExpiredException;
import net.sf.acegisecurity.Authentication;
import net.sf.acegisecurity.AuthenticationServiceException;
@@ -29,15 +31,15 @@ import net.sf.acegisecurity.LockedException;
import net.sf.acegisecurity.UserDetails;
import net.sf.acegisecurity.providers.TestingAuthenticationToken;
import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+import net.sf.acegisecurity.providers.dao.User;
+import net.sf.acegisecurity.providers.dao.UserCache;
+import net.sf.acegisecurity.providers.dao.UsernameNotFoundException;
import net.sf.acegisecurity.providers.dao.cache.EhCacheBasedUserCache;
import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DataRetrievalFailureException;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Tests {@link PasswordDaoAuthenticationProvider}.