| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
| AbstractUserDetailsAuthenticationProvider |
|
| 1.9;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 | } |