|
@@ -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.
|
|
|
+ *
|
|
|
+ * <p>
|
|
|
+ * 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
|
|
|
+ * 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>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>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>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
|
|
|
+ * confirm the most up-to-date password was used for comparison.
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @author Ben Alex
|
|
|
+ * @version $Id$
|
|
|
+ */
|
|
|
+public abstract class AbstractUserDetailsAuthenticationProvider
|
|
|
+ implements AuthenticationProvider, InitializingBean {
|
|
|
+ //~ Instance fields ========================================================
|
|
|
+
|
|
|
+ private UserCache userCache = new NullUserCache();
|
|
|
+ private boolean forcePrincipalAsString = false;
|
|
|
+
|
|
|
+ //~ Methods ================================================================
|
|
|
+
|
|
|
+ public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
|
|
|
+ this.forcePrincipalAsString = forcePrincipalAsString;
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean isForcePrincipalAsString() {
|
|
|
+ return forcePrincipalAsString;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void setUserCache(UserCache userCache) {
|
|
|
+ this.userCache = userCache;
|
|
|
+ }
|
|
|
+
|
|
|
+ public UserCache getUserCache() {
|
|
|
+ return userCache;
|
|
|
+ }
|
|
|
+
|
|
|
+ public final void afterPropertiesSet() throws Exception {
|
|
|
+ Assert.notNull(this.userCache, "A user cache must be set");
|
|
|
+ doAfterPropertiesSet();
|
|
|
+ }
|
|
|
+
|
|
|
+ public Authentication authenticate(Authentication authentication)
|
|
|
+ throws AuthenticationException {
|
|
|
+ Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,
|
|
|
+ authentication,
|
|
|
+ "Only UsernamePasswordAuthenticationToken is supported");
|
|
|
+
|
|
|
+ // Determine username
|
|
|
+ String username = (authentication.getPrincipal() == null)
|
|
|
+ ? "NONE_PROVIDED" : authentication.getName();
|
|
|
+
|
|
|
+ boolean cacheWasUsed = true;
|
|
|
+ UserDetails user = this.userCache.getUserFromCache(username);
|
|
|
+
|
|
|
+ if (user == null) {
|
|
|
+ cacheWasUsed = false;
|
|
|
+ user = retrieveUser(username,
|
|
|
+ (UsernamePasswordAuthenticationToken) authentication);
|
|
|
+ Assert.notNull(user,
|
|
|
+ "retrieveUser returned null - a violation of the interface contract");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!user.isAccountNonLocked()) {
|
|
|
+ throw new LockedException("User account is locked");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!user.isEnabled()) {
|
|
|
+ throw new DisabledException("User is disabled");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!user.isAccountNonExpired()) {
|
|
|
+ throw new AccountExpiredException("User account has expired");
|
|
|
+ }
|
|
|
+
|
|
|
+ // This check must come here, as we don't want to tell users
|
|
|
+ // about account status unless they presented the correct credentials
|
|
|
+ try {
|
|
|
+ additionalAuthenticationChecks(user,
|
|
|
+ (UsernamePasswordAuthenticationToken) authentication);
|
|
|
+ } catch (AuthenticationException exception) {
|
|
|
+ // There was a problem, so try again after checking we're using latest data
|
|
|
+ cacheWasUsed = false;
|
|
|
+ user = retrieveUser(username,
|
|
|
+ (UsernamePasswordAuthenticationToken) authentication);
|
|
|
+ additionalAuthenticationChecks(user,
|
|
|
+ (UsernamePasswordAuthenticationToken) authentication);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!user.isCredentialsNonExpired()) {
|
|
|
+ throw new CredentialsExpiredException(
|
|
|
+ "User credentials have expired");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!cacheWasUsed) {
|
|
|
+ this.userCache.putUserInCache(user);
|
|
|
+ }
|
|
|
+
|
|
|
+ Object principalToReturn = user;
|
|
|
+
|
|
|
+ if (forcePrincipalAsString) {
|
|
|
+ principalToReturn = user.getUsername();
|
|
|
+ }
|
|
|
+
|
|
|
+ return createSuccessAuthentication(principalToReturn, authentication,
|
|
|
+ user);
|
|
|
+ }
|
|
|
+
|
|
|
+ public boolean supports(Class authentication) {
|
|
|
+ return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Allows subclasses to perform any additional checks of a returned (or
|
|
|
+ * cached) <code>UserDetails</code> 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 <code>UserDetails</code> and/or
|
|
|
+ * <code>UsernamePasswordAuthenticationToken</code>, these should also
|
|
|
+ * appear in this method.
|
|
|
+ *
|
|
|
+ * @param userDetails as retrieved from the {@link #retrieveUser(String,
|
|
|
+ * UsernamePasswordAuthenticationToken)} or <code>UserCache</code>
|
|
|
+ * @param authentication the current request that needs to be authenticated
|
|
|
+ *
|
|
|
+ * @throws AuthenticationException AuthenticationException if the
|
|
|
+ * credentials could not be validated (generally a
|
|
|
+ * <code>BadCredentialsException</code>, an
|
|
|
+ * <code>AuthenticationServiceException</code>)
|
|
|
+ */
|
|
|
+ protected abstract void additionalAuthenticationChecks(
|
|
|
+ UserDetails userDetails,
|
|
|
+ UsernamePasswordAuthenticationToken authentication)
|
|
|
+ throws AuthenticationException;
|
|
|
+
|
|
|
+ protected void doAfterPropertiesSet() throws Exception {}
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Allows subclasses to actually retrieve the <code>UserDetails</code> from
|
|
|
+ * an implementation-specific location, with the option of throwing an
|
|
|
+ * <code>AuthenticationException</code> 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
|
|
|
+ * <code>UserDetails</code>).
|
|
|
+ *
|
|
|
+ * <p>
|
|
|
+ * Subclasses are not required to perform any caching, as the
|
|
|
+ * <code>AbstractUserDetailsAuthenticationProvider</code> will by default
|
|
|
+ * cache the <code>UserDetails</code>. The caching of
|
|
|
+ * <code>UserDetails</code> 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
|
|
|
+ * <code>UserDetails</code> will ever be cached) or ensure subclasses
|
|
|
+ * implement {@link #additionalAuthenticationChecks(UserDetails,
|
|
|
+ * UsernamePasswordAuthenticationToken)} to compare the credentials of a
|
|
|
+ * cached <code>UserDetails</code> with subsequent authentication
|
|
|
+ * requests.
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * <p>
|
|
|
+ * 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.
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param username The username to retrieve
|
|
|
+ * @param authentication The authentication request, which subclasses
|
|
|
+ * <em>may</em> need to perform a binding-based retrieval of the
|
|
|
+ * <code>UserDetails</code>
|
|
|
+ *
|
|
|
+ * @return the user information (never <code>null</code> - instead an
|
|
|
+ * exception should the thrown)
|
|
|
+ *
|
|
|
+ * @throws AuthenticationException if the credentials could not be
|
|
|
+ * validated (generally a <code>BadCredentialsException</code>, an
|
|
|
+ * <code>AuthenticationServiceException</code> or
|
|
|
+ * <code>UserNotFoundException</code>)
|
|
|
+ */
|
|
|
+ protected abstract UserDetails retrieveUser(String username,
|
|
|
+ UsernamePasswordAuthenticationToken authentication)
|
|
|
+ throws AuthenticationException;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates a successful {@link Authentication} object.
|
|
|
+ *
|
|
|
+ * <P>
|
|
|
+ * Protected so subclasses can override.
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * <P>
|
|
|
+ * Subclasses will usually store the original credentials the user supplied
|
|
|
+ * (not salted or encoded passwords) in the returned
|
|
|
+ * <code>Authentication</code> object.
|
|
|
+ * </p>
|
|
|
+ *
|
|
|
+ * @param principal that should be the principal in the returned object
|
|
|
+ * (defined by the {@link #isForcePrincipalAsString()} method)
|
|
|
+ * @param authentication that was presented to the
|
|
|
+ * <code>DaoAuthenticationProvider</code> for validation
|
|
|
+ * @param user that was loaded by the <code>AuthenticationDao</code>
|
|
|
+ *
|
|
|
+ * @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;
|
|
|
+ }
|
|
|
+}
|