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 public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean,
70 MessageSourceAware {
71 //~ Instance fields ================================================================================================
72
73 protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
74 private UserCache userCache = new NullUserCache();
75 private boolean forcePrincipalAsString = false;
76 protected boolean hideUserNotFoundExceptions = true;
77 private UserDetailsChecker preAuthenticationChecks = new DefaultPreAuthenticationChecks();
78 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 Assert.notNull(this.userCache, "A user cache must be set");
102 Assert.notNull(this.messages, "A message source must be set");
103 doAfterPropertiesSet();
104 }
105
106 public Authentication authenticate(Authentication authentication)
107 throws AuthenticationException {
108 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,
109 messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports",
110 "Only UsernamePasswordAuthenticationToken is supported"));
111
112 // Determine username
113 String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED" : authentication.getName();
114
115 boolean cacheWasUsed = true;
116 UserDetails user = this.userCache.getUserFromCache(username);
117
118 if (user == null) {
119 cacheWasUsed = false;
120
121 try {
122 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
123 } catch (UsernameNotFoundException notFound) {
124 if (hideUserNotFoundExceptions) {
125 throw new BadCredentialsException(messages.getMessage(
126 "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
127 } else {
128 throw notFound;
129 }
130 }
131
132 Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
133 }
134
135 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 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
141 } catch (AuthenticationException exception) {
142 if (cacheWasUsed) {
143 // There was a problem, so try again after checking
144 // we're using latest data (ie not from the cache)
145 cacheWasUsed = false;
146 user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
147 additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
148 } else {
149 throw exception;
150 }
151 }
152
153 postAuthenticationChecks.check(user);
154
155 if (!cacheWasUsed) {
156 this.userCache.putUserInCache(user);
157 }
158
159 Object principalToReturn = user;
160
161 if (forcePrincipalAsString) {
162 principalToReturn = user.getUsername();
163 }
164
165 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 UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
187 authentication.getCredentials(), user.getAuthorities());
188 result.setDetails(authentication.getDetails());
189
190 return result;
191 }
192
193 protected void doAfterPropertiesSet() throws Exception {}
194
195 public UserCache getUserCache() {
196 return userCache;
197 }
198
199 public boolean isForcePrincipalAsString() {
200 return forcePrincipalAsString;
201 }
202
203 public boolean isHideUserNotFoundExceptions() {
204 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 this.forcePrincipalAsString = forcePrincipalAsString;
240 }
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 this.hideUserNotFoundExceptions = hideUserNotFoundExceptions;
255 }
256
257 public void setMessageSource(MessageSource messageSource) {
258 this.messages = new MessageSourceAccessor(messageSource);
259 }
260
261 public void setUserCache(UserCache userCache) {
262 this.userCache = userCache;
263 }
264
265 protected UserDetailsChecker getPreAuthenticationChecks() {
266 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 this.preAuthenticationChecks = preAuthenticationChecks;
277 }
278
279 protected UserDetailsChecker getPostAuthenticationChecks() {
280 return postAuthenticationChecks;
281 }
282
283 public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) {
284 this.postAuthenticationChecks = postAuthenticationChecks;
285 }
286
287 public boolean supports(Class authentication) {
288 return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
289 }
290
291 private class DefaultPreAuthenticationChecks implements UserDetailsChecker {
292 public void check(UserDetails user) {
293 if (!user.isAccountNonLocked()) {
294 throw new LockedException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked",
295 "User account is locked"), user);
296 }
297
298 if (!user.isEnabled()) {
299 throw new DisabledException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled",
300 "User is disabled"), user);
301 }
302
303 if (!user.isAccountNonExpired()) {
304 throw new AccountExpiredException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired",
305 "User account has expired"), user);
306 }
307 }
308 }
309
310 private class DefaultPostAuthenticationChecks implements UserDetailsChecker {
311 public void check(UserDetails user) {
312 if (!user.isCredentialsNonExpired()) {
313 throw new CredentialsExpiredException(messages.getMessage(
314 "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
315 "User credentials have expired"), user);
316 }
317 }
318 }
319 }