001: /* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
002: *
003: * Licensed under the Apache License, Version 2.0 (the "License");
004: * you may not use this file except in compliance with the License.
005: * You may obtain a copy of the License at
006: *
007: * http://www.apache.org/licenses/LICENSE-2.0
008: *
009: * Unless required by applicable law or agreed to in writing, software
010: * distributed under the License is distributed on an "AS IS" BASIS,
011: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
012: * See the License for the specific language governing permissions and
013: * limitations under the License.
014: */
015:
016: package org.acegisecurity.providers.dao;
017:
018: import org.acegisecurity.AccountExpiredException;
019: import org.acegisecurity.AcegiMessageSource;
020: import org.acegisecurity.Authentication;
021: import org.acegisecurity.AuthenticationException;
022: import org.acegisecurity.BadCredentialsException;
023: import org.acegisecurity.CredentialsExpiredException;
024: import org.acegisecurity.DisabledException;
025: import org.acegisecurity.LockedException;
026:
027: import org.acegisecurity.providers.AuthenticationProvider;
028: import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
029: import org.acegisecurity.providers.dao.cache.NullUserCache;
030:
031: import org.acegisecurity.userdetails.UserDetails;
032: import org.acegisecurity.userdetails.UserDetailsService;
033: import org.acegisecurity.userdetails.UsernameNotFoundException;
034:
035: import org.springframework.beans.factory.InitializingBean;
036:
037: import org.springframework.context.MessageSource;
038: import org.springframework.context.MessageSourceAware;
039: import org.springframework.context.support.MessageSourceAccessor;
040:
041: import org.springframework.util.Assert;
042:
043: /**
044: * A base {@link AuthenticationProvider} that allows subclasses to override and work with {@link
045: * org.acegisecurity.userdetails.UserDetails} objects. The class is designed to respond to {@link
046: * UsernamePasswordAuthenticationToken} authentication requests.
047: *
048: * <p>
049: * Upon successful validation, a <code>UsernamePasswordAuthenticationToken</code> will be created and returned to the
050: * caller. The token will include as its principal either a <code>String</code> representation of the username, or the
051: * {@link UserDetails} that was returned from the authentication repository. Using <code>String</code> is appropriate
052: * if a container adapter is being used, as it expects <code>String</code> representations of the username.
053: * Using <code>UserDetails</code> is appropriate if you require access to additional properties of the authenticated
054: * user, such as email addresses, human-friendly names etc. As container adapters are not recommended to be used,
055: * and <code>UserDetails</code> implementations provide additional flexibility, by default a <code>UserDetails</code>
056: * is returned. To override this
057: * default, set the {@link #setForcePrincipalAsString} to <code>true</code>.
058: * </p>
059: * <p>Caching is handled via the <code>UserDetails</code> object being placed in the {@link UserCache}. This
060: * ensures that subsequent requests with the same username can be validated without needing to query the {@link
061: * UserDetailsService}. It should be noted that if a user appears to present an incorrect password, the {@link
062: * UserDetailsService} will be queried to confirm the most up-to-date password was used for comparison.</p>
063: *
064: * @author Ben Alex
065: * @version $Id: AbstractUserDetailsAuthenticationProvider.java 1784 2007-02-24 21:00:24Z luke_t $
066: */
067: public abstract class AbstractUserDetailsAuthenticationProvider
068: implements AuthenticationProvider, InitializingBean,
069: MessageSourceAware {
070: //~ Instance fields ================================================================================================
071:
072: protected MessageSourceAccessor messages = AcegiMessageSource
073: .getAccessor();
074: private UserCache userCache = new NullUserCache();
075: private boolean forcePrincipalAsString = false;
076: protected boolean hideUserNotFoundExceptions = true;
077:
078: //~ Methods ========================================================================================================
079:
080: /**
081: * Allows subclasses to perform any additional checks of a returned (or cached) <code>UserDetails</code>
082: * for a given authentication request. Generally a subclass will at least compare the {@link
083: * Authentication#getCredentials()} with a {@link UserDetails#getPassword()}. If custom logic is needed to compare
084: * additional properties of <code>UserDetails</code> and/or <code>UsernamePasswordAuthenticationToken</code>,
085: * these should also appear in this method.
086: *
087: * @param userDetails as retrieved from the {@link #retrieveUser(String, UsernamePasswordAuthenticationToken)} or
088: * <code>UserCache</code>
089: * @param authentication the current request that needs to be authenticated
090: *
091: * @throws AuthenticationException AuthenticationException if the credentials could not be validated (generally a
092: * <code>BadCredentialsException</code>, an <code>AuthenticationServiceException</code>)
093: */
094: protected abstract void additionalAuthenticationChecks(
095: UserDetails userDetails,
096: UsernamePasswordAuthenticationToken authentication)
097: throws AuthenticationException;
098:
099: public final void afterPropertiesSet() throws Exception {
100: Assert.notNull(this .userCache, "A user cache must be set");
101: Assert.notNull(this .messages, "A message source must be set");
102: doAfterPropertiesSet();
103: }
104:
105: public Authentication authenticate(Authentication authentication)
106: throws AuthenticationException {
107: Assert
108: .isInstanceOf(
109: UsernamePasswordAuthenticationToken.class,
110: authentication,
111: messages
112: .getMessage(
113: "AbstractUserDetailsAuthenticationProvider.onlySupports",
114: "Only UsernamePasswordAuthenticationToken is supported"));
115:
116: // Determine username
117: String username = (authentication.getPrincipal() == null) ? "NONE_PROVIDED"
118: : authentication.getName();
119:
120: boolean cacheWasUsed = true;
121: UserDetails user = this .userCache.getUserFromCache(username);
122:
123: if (user == null) {
124: cacheWasUsed = false;
125:
126: try {
127: user = retrieveUser(
128: username,
129: (UsernamePasswordAuthenticationToken) authentication);
130: } catch (UsernameNotFoundException notFound) {
131: if (hideUserNotFoundExceptions) {
132: throw new BadCredentialsException(
133: messages
134: .getMessage(
135: "AbstractUserDetailsAuthenticationProvider.badCredentials",
136: "Bad credentials"));
137: } else {
138: throw notFound;
139: }
140: }
141:
142: Assert
143: .notNull(user,
144: "retrieveUser returned null - a violation of the interface contract");
145: }
146:
147: if (!user.isAccountNonLocked()) {
148: throw new LockedException(messages.getMessage(
149: "AbstractUserDetailsAuthenticationProvider.locked",
150: "User account is locked"));
151: }
152:
153: if (!user.isEnabled()) {
154: throw new DisabledException(
155: messages
156: .getMessage(
157: "AbstractUserDetailsAuthenticationProvider.disabled",
158: "User is disabled"));
159: }
160:
161: if (!user.isAccountNonExpired()) {
162: throw new AccountExpiredException(
163: messages
164: .getMessage(
165: "AbstractUserDetailsAuthenticationProvider.expired",
166: "User account has expired"));
167: }
168:
169: // This check must come here, as we don't want to tell users
170: // about account status unless they presented the correct credentials
171: try {
172: additionalAuthenticationChecks(
173: user,
174: (UsernamePasswordAuthenticationToken) authentication);
175: } catch (AuthenticationException exception) {
176: if (cacheWasUsed) {
177: // There was a problem, so try again after checking
178: // we're using latest data (ie not from the cache)
179: cacheWasUsed = false;
180: user = retrieveUser(
181: username,
182: (UsernamePasswordAuthenticationToken) authentication);
183: additionalAuthenticationChecks(
184: user,
185: (UsernamePasswordAuthenticationToken) authentication);
186: } else {
187: throw exception;
188: }
189: }
190:
191: if (!user.isCredentialsNonExpired()) {
192: throw new CredentialsExpiredException(
193: messages
194: .getMessage(
195: "AbstractUserDetailsAuthenticationProvider.credentialsExpired",
196: "User credentials have expired"));
197: }
198:
199: if (!cacheWasUsed) {
200: this .userCache.putUserInCache(user);
201: }
202:
203: Object principalToReturn = user;
204:
205: if (forcePrincipalAsString) {
206: principalToReturn = user.getUsername();
207: }
208:
209: return createSuccessAuthentication(principalToReturn,
210: authentication, user);
211: }
212:
213: /**
214: * Creates a successful {@link Authentication} object.<p>Protected so subclasses can override.</p>
215: * <p>Subclasses will usually store the original credentials the user supplied (not salted or encoded
216: * passwords) in the returned <code>Authentication</code> object.</p>
217: *
218: * @param principal that should be the principal in the returned object (defined by the {@link
219: * #isForcePrincipalAsString()} method)
220: * @param authentication that was presented to the provider for validation
221: * @param user that was loaded by the implementation
222: *
223: * @return the successful authentication token
224: */
225: protected Authentication createSuccessAuthentication(
226: Object principal, Authentication authentication,
227: UserDetails user) {
228: // Ensure we return the original credentials the user supplied,
229: // so subsequent attempts are successful even with encoded passwords.
230: // Also ensure we return the original getDetails(), so that future
231: // authentication events after cache expiry contain the details
232: UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(
233: principal, authentication.getCredentials(), user
234: .getAuthorities());
235: result.setDetails(authentication.getDetails());
236:
237: return result;
238: }
239:
240: protected void doAfterPropertiesSet() throws Exception {
241: }
242:
243: public UserCache getUserCache() {
244: return userCache;
245: }
246:
247: public boolean isForcePrincipalAsString() {
248: return forcePrincipalAsString;
249: }
250:
251: public boolean isHideUserNotFoundExceptions() {
252: return hideUserNotFoundExceptions;
253: }
254:
255: /**
256: * Allows subclasses to actually retrieve the <code>UserDetails</code> from an implementation-specific
257: * location, with the option of throwing an <code>AuthenticationException</code> immediately if the presented
258: * credentials are incorrect (this is especially useful if it is necessary to bind to a resource as the user in
259: * order to obtain or generate a <code>UserDetails</code>).<p>Subclasses are not required to perform any
260: * caching, as the <code>AbstractUserDetailsAuthenticationProvider</code> will by default cache the
261: * <code>UserDetails</code>. The caching of <code>UserDetails</code> does present additional complexity as this
262: * means subsequent requests that rely on the cache will need to still have their credentials validated, even if
263: * the correctness of credentials was assured by subclasses adopting a binding-based strategy in this method.
264: * Accordingly it is important that subclasses either disable caching (if they want to ensure that this method is
265: * the only method that is capable of authenticating a request, as no <code>UserDetails</code> will ever be
266: * cached) or ensure subclasses implement {@link #additionalAuthenticationChecks(UserDetails,
267: * UsernamePasswordAuthenticationToken)} to compare the credentials of a cached <code>UserDetails</code> with
268: * subsequent authentication requests.</p>
269: * <p>Most of the time subclasses will not perform credentials inspection in this method, instead
270: * performing it in {@link #additionalAuthenticationChecks(UserDetails, UsernamePasswordAuthenticationToken)} so
271: * that code related to credentials validation need not be duplicated across two methods.</p>
272: *
273: * @param username The username to retrieve
274: * @param authentication The authentication request, which subclasses <em>may</em> need to perform a binding-based
275: * retrieval of the <code>UserDetails</code>
276: *
277: * @return the user information (never <code>null</code> - instead an exception should the thrown)
278: *
279: * @throws AuthenticationException if the credentials could not be validated (generally a
280: * <code>BadCredentialsException</code>, an <code>AuthenticationServiceException</code> or
281: * <code>UsernameNotFoundException</code>)
282: */
283: protected abstract UserDetails retrieveUser(String username,
284: UsernamePasswordAuthenticationToken authentication)
285: throws AuthenticationException;
286:
287: public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
288: this .forcePrincipalAsString = forcePrincipalAsString;
289: }
290:
291: /**
292: * By default the <code>AbstractUserDetailsAuthenticationProvider</code> throws a
293: * <code>BadCredentialsException</code> if a username is not found or the password is incorrect. Setting this
294: * property to <code>false</code> will cause <code>UsernameNotFoundException</code>s to be thrown instead for the
295: * former. Note this is considered less secure than throwing <code>BadCredentialsException</code> for both
296: * exceptions.
297: *
298: * @param hideUserNotFoundExceptions set to <code>false</code> if you wish <code>UsernameNotFoundException</code>s
299: * to be thrown instead of the non-specific <code>BadCredentialsException</code> (defaults to
300: * <code>true</code>)
301: */
302: public void setHideUserNotFoundExceptions(
303: boolean hideUserNotFoundExceptions) {
304: this .hideUserNotFoundExceptions = hideUserNotFoundExceptions;
305: }
306:
307: public void setMessageSource(MessageSource messageSource) {
308: this .messages = new MessageSourceAccessor(messageSource);
309: }
310:
311: public void setUserCache(UserCache userCache) {
312: this .userCache = userCache;
313: }
314:
315: public boolean supports(Class authentication) {
316: return (UsernamePasswordAuthenticationToken.class
317: .isAssignableFrom(authentication));
318: }
319: }
|