Coverage Report - org.acegisecurity.providers.dao.AbstractUserDetailsAuthenticationProvider
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractUserDetailsAuthenticationProvider
91% 
100% 
1.9
 
 1  
 /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 2  
  *
 3  
  * Licensed under the Apache License, Version 2.0 (the "License");
 4  
  * you may not use this file except in compliance with the License.
 5  
  * You may obtain a copy of the License at
 6  
  *
 7  
  *     http://www.apache.org/licenses/LICENSE-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing, software
 10  
  * distributed under the License is distributed on an "AS IS" BASIS,
 11  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  
  * See the License for the specific language governing permissions and
 13  
  * limitations under the License.
 14  
  */
 15  
 
 16  
 package org.acegisecurity.providers.dao;
 17  
 
 18  
 import org.acegisecurity.AccountExpiredException;
 19  
 import org.acegisecurity.AcegiMessageSource;
 20  
 import org.acegisecurity.Authentication;
 21  
 import org.acegisecurity.AuthenticationException;
 22  
 import org.acegisecurity.BadCredentialsException;
 23  
 import org.acegisecurity.CredentialsExpiredException;
 24  
 import org.acegisecurity.DisabledException;
 25  
 import org.acegisecurity.LockedException;
 26  
 
 27  
 import org.acegisecurity.providers.AuthenticationProvider;
 28  
 import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
 29  
 import org.acegisecurity.providers.dao.cache.NullUserCache;
 30  
 
 31  
 import org.acegisecurity.userdetails.UserDetails;
 32  
 import org.acegisecurity.userdetails.UserDetailsService;
 33  
 import org.acegisecurity.userdetails.UsernameNotFoundException;
 34  
 import org.acegisecurity.userdetails.UserDetailsChecker;
 35  
 
 36  
 import org.springframework.beans.factory.InitializingBean;
 37  
 
 38  
 import org.springframework.context.MessageSource;
 39  
 import org.springframework.context.MessageSourceAware;
 40  
 import org.springframework.context.support.MessageSourceAccessor;
 41  
 
 42  
 import org.springframework.util.Assert;
 43  
 
 44  
 
 45  
 /**
 46  
  * A base {@link AuthenticationProvider} that allows subclasses to override and work with {@link
 47  
  * org.acegisecurity.userdetails.UserDetails} objects. The class is designed to respond to {@link
 48  
  * UsernamePasswordAuthenticationToken} authentication requests.
 49  
  *
 50  
  * <p>
 51  
  * Upon successful validation, a <code>UsernamePasswordAuthenticationToken</code> will be created and returned to the
 52  
  * caller. The token will include as its principal either a <code>String</code> representation of the username, or the
 53  
  * {@link UserDetails} that was returned from the authentication repository. Using <code>String</code> is appropriate
 54  
  * if a container adapter is being used, as it expects <code>String</code> representations of the username.
 55  
  * Using <code>UserDetails</code> is appropriate if you require access to additional properties of the authenticated
 56  
  * user, such as email addresses, human-friendly names etc. As container adapters are not recommended to be used,
 57  
  * and <code>UserDetails</code> implementations provide additional flexibility, by default a <code>UserDetails</code>
 58  
  * is returned. To override this
 59  
  * default, set the {@link #setForcePrincipalAsString} to <code>true</code>.
 60  
  * </p>
 61  
  *  <p>Caching is handled via the <code>UserDetails</code> object being placed in the {@link UserCache}. This
 62  
  * ensures that subsequent requests with the same username can be validated without needing to query the {@link
 63  
  * UserDetailsService}. It should be noted that if a user appears to present an incorrect password, the {@link
 64  
  * UserDetailsService} will be queried to confirm the most up-to-date password was used for comparison.</p>
 65  
  *
 66  
  * @author Ben Alex
 67  
  * @version $Id: AbstractUserDetailsAuthenticationProvider.java 2654 2008-02-18 20:44:09Z luke_t $
 68  
  */
 69  59
 public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean,
 70  
         MessageSourceAware {
 71  
     //~ Instance fields ================================================================================================
 72  
 
 73  59
     protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
 74  59
     private UserCache userCache = new NullUserCache();
 75  59
     private boolean forcePrincipalAsString = false;
 76  59
     protected boolean hideUserNotFoundExceptions = true;
 77  59
     private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
 78  59
     private UserDetailsChecker postAuthenticationChecks = new DefaultPostAuthenticationChecks();
 79  
 
 80  
     //~ Methods ========================================================================================================
 81  
 
 82  
     /**
 83  
      * Allows subclasses to perform any additional checks of a returned (or cached) <code>UserDetails</code>
 84  
      * for a given authentication request. Generally a subclass will at least compare the {@link
 85  
      * Authentication#getCredentials()} with a {@link UserDetails#getPassword()}. If custom logic is needed to compare
 86  
      * additional properties of <code>UserDetails</code> and/or <code>UsernamePasswordAuthenticationToken</code>,
 87  
      * these should also appear in this method.
 88  
      *
 89  
      * @param userDetails as retrieved from the {@link #retrieveUser(String, UsernamePasswordAuthenticationToken)} or
 90  
      *        <code>UserCache</code>
 91  
      * @param authentication the current request that needs to be authenticated
 92  
      *
 93  
      * @throws AuthenticationException AuthenticationException if the credentials could not be validated (generally a
 94  
      *         <code>BadCredentialsException</code>, an <code>AuthenticationServiceException</code>)
 95  
      */
 96  
     protected abstract void additionalAuthenticationChecks(UserDetails userDetails,
 97  
         UsernamePasswordAuthenticationToken authentication)
 98  
         throws AuthenticationException;
 99  
 
 100  
     public final void afterPropertiesSet() throws Exception {
 101  7
         Assert.notNull(this.userCache, "A user cache must be set");
 102  5
         Assert.notNull(this.messages, "A message source must be set");
 103  5
         doAfterPropertiesSet();
 104  3
     }
 105  
 
 106  
     public Authentication authenticate(Authentication authentication)
 107  
         throws AuthenticationException {
 108  46
         Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
 109  
             messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
 110  
                 "Only UsernamePasswordAuthenticationToken is supported"));
 111  
 
 112  
         // Determine username
 113  45
         String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
 114  
 
 115  45
         boolean cacheWasUsed = true;
 116  45
         UserDetails user = this.userCache.getUserFromCache(username);
 117  
 
 118  45
         if (user == null) {
 119  42
             cacheWasUsed = false;
 120  
 
 121  
             try {
 122  42
                 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
 123  10
             } catch (UsernameNotFoundException notFound) {
 124  10
                 if (hideUserNotFoundExceptions) {
 125  8
                     throw new BadCredentialsException(messages.getMessage(
 126  
                             "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
 127  
                 } else {
 128  2
                     throw notFound;
 129  
                 }
 130  28
             }
 131  
 
 132  28
             Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
 133  
         }
 134  
 
 135  31
         preAuthenticationChecks.check(user);
 136  
 
 137  
         // This check must come here, as we don't want to tell users
 138  
         // about account status unless they presented the correct credentials
 139  
         try {
 140  25
             additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
 141  8
         } catch (AuthenticationException exception) {
 142  8
             if (cacheWasUsed) {
 143  
                 // There was a problem, so try again after checking
 144  
                 // we're using latest data (ie not from the cache)
 145  1
                 cacheWasUsed = false;
 146  1
                 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
 147  1
                 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
 148  
             } else {
 149  7
                 throw exception;
 150  
             }
 151  17
         }
 152  
 
 153  18
         postAuthenticationChecks.check(user);
 154  
 
 155  17
         if (!cacheWasUsed) {
 156  15
             this.userCache.putUserInCache(user);
 157  
         }
 158  
 
 159  17
         Object principalToReturn = user;
 160  
 
 161  17
         if (forcePrincipalAsString) {
 162  2
             principalToReturn = user.getUsername();
 163  
         }
 164  
 
 165  17
         return createSuccessAuthentication(principalToReturn, authentication, user);
 166  
     }
 167  
 
 168  
     /**
 169  
      * Creates a successful {@link Authentication} object.<p>Protected so subclasses can override.</p>
 170  
      *  <p>Subclasses will usually store the original credentials the user supplied (not salted or encoded
 171  
      * passwords) in the returned <code>Authentication</code> object.</p>
 172  
      *
 173  
      * @param principal that should be the principal in the returned object (defined by the {@link
 174  
      *        #isForcePrincipalAsString()} method)
 175  
      * @param authentication that was presented to the provider for validation
 176  
      * @param user that was loaded by the implementation
 177  
      *
 178  
      * @return the successful authentication token
 179  
      */
 180  
     protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
 181  
         UserDetails user) {
 182  
         // Ensure we return the original credentials the user supplied,
 183  
         // so subsequent attempts are successful even with encoded passwords.
 184  
         // Also ensure we return the original getDetails(), so that future
 185  
         // authentication events after cache expiry contain the details
 186  17
         UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
 187  
                 authentication.getCredentials(), user.getAuthorities());
 188  17
         result.setDetails(authentication.getDetails());
 189  
 
 190  17
         return result;
 191  
     }
 192  
 
 193  0
     protected void doAfterPropertiesSet() throws Exception {}
 194  
 
 195  
     public UserCache getUserCache() {
 196  4
         return userCache;
 197  
     }
 198  
 
 199  
     public boolean isForcePrincipalAsString() {
 200  4
         return forcePrincipalAsString;
 201  
     }
 202  
 
 203  
     public boolean isHideUserNotFoundExceptions() {
 204  2
         return hideUserNotFoundExceptions;
 205  
     }
 206  
 
 207  
     /**
 208  
      * Allows subclasses to actually retrieve the <code>UserDetails</code> from an implementation-specific
 209  
      * location, with the option of throwing an <code>AuthenticationException</code> immediately if the presented
 210  
      * credentials are incorrect (this is especially useful if it is necessary to bind to a resource as the user in
 211  
      * order to obtain or generate a <code>UserDetails</code>).<p>Subclasses are not required to perform any
 212  
      * caching, as the <code>AbstractUserDetailsAuthenticationProvider</code> will by default cache the
 213  
      * <code>UserDetails</code>. The caching of <code>UserDetails</code> does present additional complexity as this
 214  
      * means subsequent requests that rely on the cache will need to still have their credentials validated, even if
 215  
      * the correctness of credentials was assured by subclasses adopting a binding-based strategy in this method.
 216  
      * Accordingly it is important that subclasses either disable caching (if they want to ensure that this method is
 217  
      * the only method that is capable of authenticating a request, as no <code>UserDetails</code> will ever be
 218  
      * cached) or ensure subclasses implement {@link #additionalAuthenticationChecks(UserDetails,
 219  
      * UsernamePasswordAuthenticationToken)} to compare the credentials of a cached <code>UserDetails</code> with
 220  
      * subsequent authentication requests.</p>
 221  
      *  <p>Most of the time subclasses will not perform credentials inspection in this method, instead
 222  
      * performing it in {@link #additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)} so
 223  
      * that code related to credentials validation need not be duplicated across two methods.</p>
 224  
      *
 225  
      * @param username The username to retrieve
 226  
      * @param authentication The authentication request, which subclasses <em>may</em> need to perform a binding-based
 227  
      *        retrieval of the <code>UserDetails</code>
 228  
      *
 229  
      * @return the user information (never <code>null</code> - instead an exception should the thrown)
 230  
      *
 231  
      * @throws AuthenticationException if the credentials could not be validated (generally a
 232  
      *         <code>BadCredentialsException</code>, an <code>AuthenticationServiceException</code> or
 233  
      *         <code>UsernameNotFoundException</code>)
 234  
      */
 235  
     protected abstract UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
 236  
         throws AuthenticationException;
 237  
 
 238  
     public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
 239  4
         this.forcePrincipalAsString = forcePrincipalAsString;
 240  4
     }
 241  
 
 242  
     /**
 243  
      * By default the <code>AbstractUserDetailsAuthenticationProvider</code> throws a
 244  
      * <code>BadCredentialsException</code> if a username is not found or the password is incorrect. Setting this
 245  
      * property to <code>false</code> will cause <code>UsernameNotFoundException</code>s to be thrown instead for the
 246  
      * former. Note this is considered less secure than throwing <code>BadCredentialsException</code> for both
 247  
      * exceptions.
 248  
      *
 249  
      * @param hideUserNotFoundExceptions set to <code>false</code> if you wish <code>UsernameNotFoundException</code>s
 250  
      *        to be thrown instead of the non-specific <code>BadCredentialsException</code> (defaults to
 251  
      *        <code>true</code>)
 252  
      */
 253  
     public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) {
 254  2
         this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
 255  2
     }
 256  
 
 257  
     public void setMessageSource(MessageSource messageSource) {
 258  1
         this.messages = new MessageSourceAccessor(messageSource);
 259  1
     }
 260  
 
 261  
     public void setUserCache(UserCache userCache) {
 262  35
         this.userCache = userCache;
 263  35
     }
 264  
 
 265  
     protected UserDetailsChecker getPreAuthenticationChecks() {
 266  0
         return preAuthenticationChecks;
 267  
     }
 268  
 
 269  
     /**
 270  
      * Sets the policy will be used to verify the status of the loaded <tt>UserDetails</tt> <em>before</em>
 271  
      * validation of the credentials takes place.
 272  
      *
 273  
      * @param preAuthenticationChecks strategy to be invoked prior to authentication. 
 274  
      */
 275  
     public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) {
 276  0
         this.preAuthenticationChecks = preAuthenticationChecks;
 277  0
     }
 278  
 
 279  
     protected UserDetailsChecker getPostAuthenticationChecks() {
 280  0
         return postAuthenticationChecks;
 281  
     }
 282  
 
 283  
     public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
 284  0
         this.postAuthenticationChecks = postAuthenticationChecks;
 285  0
     }    
 286  
 
 287  
     public boolean supports(Class authentication) {
 288  14
         return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
 289  
     }
 290  
 
 291  118
     private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
 292  
         public void check(UserDetails user) {
 293  31
             if (!user.isAccountNonLocked()) {
 294  2
                 throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
 295  
                         "User account is locked"), user);
 296  
             }
 297  
 
 298  29
             if (!user.isEnabled()) {
 299  2
                 throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
 300  
                         "User is disabled"), user);
 301  
             }
 302  
 
 303  27
             if (!user.isAccountNonExpired()) {
 304  2
                 throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
 305  
                         "User account has expired"), user);
 306  
             }
 307  25
         }
 308  
     }
 309  
 
 310  59
     private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
 311  
         public void check(UserDetails user) {
 312  18
             if (!user.isCredentialsNonExpired()) {
 313  1
                 throw new CredentialsExpiredException(messages.getMessage(
 314  
                         "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
 315  
                         "User credentials have expired"), user);
 316  
             }
 317  17
         }
 318  
     }
 319  
 }