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 }