1
2
3
4
5
6
7
8
9
10
11
12
13
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
72
73 protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
74 private UserCache userCache = new NullUserCache();
75 private boolean forcePrincipalAsString = false;
76
77
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
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
151
152 try {
153 additionalAuthenticationChecks(user,
154 (UsernamePasswordAuthenticationToken) authentication);
155 } catch (AuthenticationException exception) {
156
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
208
209
210
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 }