View Javadoc

    1   /* Copyright 2004, 2005 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.CredentialsExpiredException;
    23  import org.acegisecurity.DisabledException;
    24  import org.acegisecurity.LockedException;
    25  import org.acegisecurity.providers.AuthenticationProvider;
    26  import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
    27  import org.acegisecurity.providers.dao.cache.NullUserCache;
    28  import org.acegisecurity.userdetails.UserDetails;
    29  import org.acegisecurity.userdetails.UserDetailsService;
    30  import org.springframework.beans.factory.InitializingBean;
    31  import org.springframework.context.MessageSource;
    32  import org.springframework.context.MessageSourceAware;
    33  import org.springframework.context.support.MessageSourceAccessor;
    34  import org.springframework.util.Assert;
    35
    36
    37  /***
    38   * A base {@link AuthenticationProvider} that allows subclasses to override and
    39   * work with {@link org.acegisecurity.userdetails.UserDetails} objects. The class is
    40   * designed to respond to {@link UsernamePasswordAuthenticationToken}
    41   * authentication requests.
    42   *
    43   * <p>
    44   * Upon successful validation, a
    45   * <code>UsernamePasswordAuthenticationToken</code> will be created and
    46   * returned to the caller. The token will include as its principal either a
    47   * <code>String</code> representation of the username, or the {@link
    48   * UserDetails} that was returned from the authentication repository. Using
    49   * <code>String</code> is appropriate if a container adapter is being used, as
    50   * it expects <code>String</code> representations of the username. Using
    51   * <code>UserDetails</code> is appropriate if you require access to additional
    52   * properties of the authenticated user, such as email addresses,
    53   * human-friendly names etc. As container adapters are not recommended to be
    54   * used, and <code>UserDetails</code> implementations provide additional
    55   * flexibility, by default a <code>UserDetails</code> is returned. To override
    56   * this default, set the {@link #setForcePrincipalAsString} to
    57   * <code>true</code>.
    58   * </p>
    59   *
    60   * <P>
    61   * Caching is handled via the <code>UserDetails</code> object being placed in
    62   * the {@link UserCache}. This ensures that subsequent requests with the same
    63   * username can be validated without needing to query the {@link
    64   * UserDetailsService}. It should be noted that if a user appears to present an
    65   * incorrect password, the {@link UserDetailsService} will be queried to
    66   * confirm the most up-to-date password was used for comparison.
    67   * </p>
    68   */
    69  public abstract class AbstractUserDetailsAuthenticationProvider
    70      implements AuthenticationProvider, InitializingBean, MessageSourceAware {
    71      //~ Instance fields ========================================================
    72
    73      protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
    74      private UserCache userCache = new NullUserCache();
    75      private boolean forcePrincipalAsString = false;
    76
    77      //~ Methods ================================================================
    78
    79      /***
    80       * Allows subclasses to perform any additional checks of a returned (or
    81       * cached) <code>UserDetails</code> for a given authentication request.
    82       * Generally a subclass will at least compare the {@link
    83       * Authentication#getCredentials()} with a {@link
    84       * UserDetails#getPassword()}. If custom logic is needed to compare
    85       * additional properties of <code>UserDetails</code> and/or
    86       * <code>UsernamePasswordAuthenticationToken</code>, these should also
    87       * appear in this method.
    88       *
    89       * @param userDetails as retrieved from the {@link #retrieveUser(String,
    90       *        UsernamePasswordAuthenticationToken)} or <code>UserCache</code>
    91       * @param authentication the current request that needs to be authenticated
    92       *
    93       * @throws AuthenticationException AuthenticationException if the
    94       *         credentials could not be validated (generally a
    95       *         <code>BadCredentialsException</code>, an
    96       *         <code>AuthenticationServiceException</code>)
    97       */
    98      protected abstract void additionalAuthenticationChecks(
    99          UserDetails userDetails,
    100         UsernamePasswordAuthenticationToken authentication)
    101         throws AuthenticationException;
    102
    103     public final void afterPropertiesSet() throws Exception {
    104         Assert.notNull(this.userCache, "A user cache must be set");
    105         Assert.notNull(this.messages, "A message source must be set");
    106         doAfterPropertiesSet();
    107     }
    108
    109     public Authentication authenticate(Authentication authentication)
    110         throws AuthenticationException {
    111         Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class,
    112             authentication,
    113             messages.getMessage(
    114                 "AbstractUserDetailsAuthenticationProvider.onlySupports",
    115                 "Only UsernamePasswordAuthenticationToken is supported"));
    116
    117         // Determine username
    118         String username = (authentication.getPrincipal() == null)
    119             ? "NONE_PROVIDED" : authentication.getName();
    120
    121         boolean cacheWasUsed = true;
    122         UserDetails user = this.userCache.getUserFromCache(username);
    123
    124         if (user == null) {
    125             cacheWasUsed = false;
    126             user = retrieveUser(username,
    127                     (UsernamePasswordAuthenticationToken) authentication);
    128             Assert.notNull(user,
    129                 "retrieveUser returned null - a violation of the interface contract");
    130         }
    131
    132         if (!user.isAccountNonLocked()) {
    133             throw new LockedException(messages.getMessage(
    134                     "AbstractUserDetailsAuthenticationProvider.locked",
    135                     "User account is locked"));
    136         }
    137
    138         if (!user.isEnabled()) {
    139             throw new DisabledException(messages.getMessage(
    140                     "AbstractUserDetailsAuthenticationProvider.disabled",
    141                     "User is disabled"));
    142         }
    143
    144         if (!user.isAccountNonExpired()) {
    145             throw new AccountExpiredException(messages.getMessage(
    146                     "AbstractUserDetailsAuthenticationProvider.expired",
    147                     "User account has expired"));
    148         }
    149
    150         // This check must come here, as we don't want to tell users
    151         // about account status unless they presented the correct credentials
    152         try {
    153             additionalAuthenticationChecks(user,
    154                 (UsernamePasswordAuthenticationToken) authentication);
    155         } catch (AuthenticationException exception) {
    156             // There was a problem, so try again after checking we're using latest data
    157             cacheWasUsed = false;
    158             user = retrieveUser(username,
    159                     (UsernamePasswordAuthenticationToken) authentication);
    160             additionalAuthenticationChecks(user,
    161                 (UsernamePasswordAuthenticationToken) authentication);
    162         }
    163
    164         if (!user.isCredentialsNonExpired()) {
    165             throw new CredentialsExpiredException(messages.getMessage(
    166                     "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
    167                     "User credentials have expired"));
    168         }
    169
    170         if (!cacheWasUsed) {
    171             this.userCache.putUserInCache(user);
    172         }
    173
    174         Object principalToReturn = user;
    175
    176         if (forcePrincipalAsString) {
    177             principalToReturn = user.getUsername();
    178         }
    179
    180         return createSuccessAuthentication(principalToReturn, authentication,
    181             user);
    182     }
    183
    184     /***
    185      * Creates a successful {@link Authentication} object.
    186      *
    187      * <P>
    188      * Protected so subclasses can override.
    189      * </p>
    190      *
    191      * <P>
    192      * Subclasses will usually store the original credentials the user supplied
    193      * (not salted or encoded passwords) in the returned
    194      * <code>Authentication</code> object.
    195      * </p>
    196      *
    197      * @param principal that should be the principal in the returned object
    198      *        (defined by the {@link #isForcePrincipalAsString()} method)
    199      * @param authentication that was presented to the
    200      *        <code>DaoAuthenticationProvider</code> for validation
    201      * @param user that was loaded by the <code>AuthenticationDao</code>
    202      *
    203      * @return the successful authentication token
    204      */
    205     protected Authentication createSuccessAuthentication(Object principal,
    206         Authentication authentication, UserDetails user) {
    207         // Ensure we return the original credentials the user supplied,
    208         // so subsequent attempts are successful even with encoded passwords.
    209         // Also ensure we return the original getDetails(), so that future
    210         // authentication events after cache expiry contain the details
    211         UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
    212                 authentication.getCredentials(), user.getAuthorities());
    213         result.setDetails((authentication.getDetails() != null)
    214             ? authentication.getDetails() : null);
    215
    216         return result;
    217     }
    218
    219     protected void doAfterPropertiesSet() throws Exception {}
    220
    221     public UserCache getUserCache() {
    222         return userCache;
    223     }
    224
    225     public boolean isForcePrincipalAsString() {
    226         return forcePrincipalAsString;
    227     }
    228
    229     /***
    230      * Allows subclasses to actually retrieve the <code>UserDetails</code> from
    231      * an implementation-specific location, with the option of throwing an
    232      * <code>AuthenticationException</code> immediately if the presented
    233      * credentials are incorrect (this is especially useful if it is necessary
    234      * to bind to a resource as the user in order to obtain or generate a
    235      * <code>UserDetails</code>).
    236      *
    237      * <p>
    238      * Subclasses are not required to perform any caching, as the
    239      * <code>AbstractUserDetailsAuthenticationProvider</code> will by default
    240      * cache the <code>UserDetails</code>. The caching of
    241      * <code>UserDetails</code> does present additional complexity as this
    242      * means subsequent requests that rely on the cache will need to still
    243      * have their credentials validated, even if the correctness of
    244      * credentials was assured by subclasses adopting a binding-based strategy
    245      * in this method. Accordingly it is important that subclasses either
    246      * disable caching (if they want to ensure that this method is the only
    247      * method that is capable of authenticating a request, as no
    248      * <code>UserDetails</code> will ever be cached) or ensure subclasses
    249      * implement {@link #additionalAuthenticationChecks(UserDetails,
    250      * UsernamePasswordAuthenticationToken)} to compare the credentials of a
    251      * cached <code>UserDetails</code> with subsequent authentication
    252      * requests.
    253      * </p>
    254      *
    255      * <p>
    256      * Most of the time subclasses will not perform credentials inspection in
    257      * this method, instead performing it in {@link
    258      * #additionalAuthenticationChecks(UserDetails,
    259      * UsernamePasswordAuthenticationToken)} so that code related to
    260      * credentials validation need not be duplicated across two methods.
    261      * </p>
    262      *
    263      * @param username The username to retrieve
    264      * @param authentication The authentication request, which subclasses
    265      *        <em>may</em> need to perform a binding-based retrieval of the
    266      *        <code>UserDetails</code>
    267      *
    268      * @return the user information (never <code>null</code> - instead an
    269      *         exception should the thrown)
    270      *
    271      * @throws AuthenticationException if the credentials could not be
    272      *         validated (generally a <code>BadCredentialsException</code>, an
    273      *         <code>AuthenticationServiceException</code> or
    274      *         <code>UserNotFoundException</code>)
    275      */
    276     protected abstract UserDetails retrieveUser(String username,
    277         UsernamePasswordAuthenticationToken authentication)
    278         throws AuthenticationException;
    279
    280     public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
    281         this.forcePrincipalAsString = forcePrincipalAsString;
    282     }
    283
    284     public void setMessageSource(MessageSource messageSource) {
    285         this.messages = new MessageSourceAccessor(messageSource);
    286     }
    287
    288     public void setUserCache(UserCache userCache) {
    289         this.userCache = userCache;
    290     }
    291
    292     public boolean supports(Class authentication) {
    293         return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    294     }
    295 }