|
|||||||||||||||||||
| Source file | Conditionals | Statements | Methods | TOTAL | |||||||||||||||
| AbstractUserDetailsAuthenticationProvider.java | 100% | 100% | 90% | 98.5% |
|
||||||||||||||
| 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 | 37 | public final void afterPropertiesSet() throws Exception { |
| 104 | 37 | Assert.notNull(this.userCache, "A user cache must be set"); |
| 105 | 36 | Assert.notNull(this.messages, "A message source must be set"); |
| 106 | 36 | doAfterPropertiesSet(); |
| 107 | } | |
| 108 | ||
| 109 | 25 | public Authentication authenticate(Authentication authentication) |
| 110 | throws AuthenticationException { | |
| 111 | 25 | Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, |
| 112 | authentication, | |
| 113 | messages.getMessage( | |
| 114 | "AbstractUserDetailsAuthenticationProvider.onlySupports", | |
| 115 | "Only UsernamePasswordAuthenticationToken is supported")); | |
| 116 | ||
| 117 | // Determine username | |
| 118 | 25 | String username = (authentication.getPrincipal() == null) |
| 119 | ? "NONE_PROVIDED" : authentication.getName(); | |
| 120 | ||
| 121 | 25 | boolean cacheWasUsed = true; |
| 122 | 25 | UserDetails user = this.userCache.getUserFromCache(username); |
| 123 | ||
| 124 | 25 | if (user == null) { |
| 125 | 23 | cacheWasUsed = false; |
| 126 | 23 | user = retrieveUser(username, |
| 127 | (UsernamePasswordAuthenticationToken) authentication); | |
| 128 | 15 | Assert.notNull(user, |
| 129 | "retrieveUser returned null - a violation of the interface contract"); | |
| 130 | } | |
| 131 | ||
| 132 | 17 | if (!user.isAccountNonLocked()) { |
| 133 | 1 | throw new LockedException(messages.getMessage( |
| 134 | "AbstractUserDetailsAuthenticationProvider.locked", | |
| 135 | "User account is locked")); | |
| 136 | } | |
| 137 | ||
| 138 | 16 | if (!user.isEnabled()) { |
| 139 | 1 | throw new DisabledException(messages.getMessage( |
| 140 | "AbstractUserDetailsAuthenticationProvider.disabled", | |
| 141 | "User is disabled")); | |
| 142 | } | |
| 143 | ||
| 144 | 15 | if (!user.isAccountNonExpired()) { |
| 145 | 1 | 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 | 14 | try { |
| 153 | 14 | 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 | 5 | cacheWasUsed = false; |
| 158 | 5 | user = retrieveUser(username, |
| 159 | (UsernamePasswordAuthenticationToken) authentication); | |
| 160 | 5 | additionalAuthenticationChecks(user, |
| 161 | (UsernamePasswordAuthenticationToken) authentication); | |
| 162 | } | |
| 163 | ||
| 164 | 10 | if (!user.isCredentialsNonExpired()) { |
| 165 | 1 | throw new CredentialsExpiredException(messages.getMessage( |
| 166 | "AbstractUserDetailsAuthenticationProvider.credentialsExpired", | |
| 167 | "User credentials have expired")); | |
| 168 | } | |
| 169 | ||
| 170 | 9 | if (!cacheWasUsed) { |
| 171 | 8 | this.userCache.putUserInCache(user); |
| 172 | } | |
| 173 | ||
| 174 | 9 | Object principalToReturn = user; |
| 175 | ||
| 176 | 9 | if (forcePrincipalAsString) { |
| 177 | 1 | principalToReturn = user.getUsername(); |
| 178 | } | |
| 179 | ||
| 180 | 9 | 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 | 9 | 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 | 9 | UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, |
| 212 | authentication.getCredentials(), user.getAuthorities()); | |
| 213 | 9 | result.setDetails((authentication.getDetails() != null) |
| 214 | ? authentication.getDetails() : null); | |
| 215 | ||
| 216 | 9 | return result; |
| 217 | } | |
| 218 | ||
| 219 | 0 | protected void doAfterPropertiesSet() throws Exception {} |
| 220 | ||
| 221 | 2 | public UserCache getUserCache() { |
| 222 | 2 | return userCache; |
| 223 | } | |
| 224 | ||
| 225 | 2 | public boolean isForcePrincipalAsString() { |
| 226 | 2 | 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 | 2 | public void setForcePrincipalAsString(boolean forcePrincipalAsString) { |
| 281 | 2 | this.forcePrincipalAsString = forcePrincipalAsString; |
| 282 | } | |
| 283 | ||
| 284 | 34 | public void setMessageSource(MessageSource messageSource) { |
| 285 | 34 | this.messages = new MessageSourceAccessor(messageSource); |
| 286 | } | |
| 287 | ||
| 288 | 19 | public void setUserCache(UserCache userCache) { |
| 289 | 19 | this.userCache = userCache; |
| 290 | } | |
| 291 | ||
| 292 | 7 | public boolean supports(Class authentication) { |
| 293 | 7 | return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); |
| 294 | } | |
| 295 | } |
|
||||||||||