Source code: net/sf/acegisecurity/providers/dao/PasswordDaoAuthenticationProvider.java
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 net.sf.acegisecurity.providers.dao;
17
18 import net.sf.acegisecurity.AccountExpiredException;
19 import net.sf.acegisecurity.Authentication;
20 import net.sf.acegisecurity.AuthenticationException;
21 import net.sf.acegisecurity.AuthenticationServiceException;
22 import net.sf.acegisecurity.BadCredentialsException;
23 import net.sf.acegisecurity.CredentialsExpiredException;
24 import net.sf.acegisecurity.DisabledException;
25 import net.sf.acegisecurity.GrantedAuthority;
26 import net.sf.acegisecurity.LockedException;
27 import net.sf.acegisecurity.UserDetails;
28 import net.sf.acegisecurity.providers.AuthenticationProvider;
29 import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken;
30 import net.sf.acegisecurity.providers.dao.cache.NullUserCache;
31 import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureAccountExpiredEvent;
32 import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureAccountLockedEvent;
33 import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureCredentialsExpiredEvent;
34 import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureDisabledEvent;
35 import net.sf.acegisecurity.providers.dao.event.AuthenticationFailureUsernameOrPasswordEvent;
36 import net.sf.acegisecurity.providers.dao.event.AuthenticationSuccessEvent;
37
38 import org.springframework.beans.BeansException;
39 import org.springframework.beans.factory.InitializingBean;
40
41 import org.springframework.context.ApplicationContext;
42 import org.springframework.context.ApplicationContextAware;
43
44 import org.springframework.dao.DataAccessException;
45 import org.springframework.util.Assert;
46
47
48 /**
49 * An {@link AuthenticationProvider} implementation that retrieves user details
50 * from a {@link PasswordAuthenticationDao}.
51 *
52 * <p>
53 * This <code>AuthenticationProvider</code> is capable of validating {@link
54 * UsernamePasswordAuthenticationToken} requests containing the correct
55 * username, password and when the user is not disabled.
56 * </p>
57 *
58 * <p>
59 * Unlike {@link DaoAuthenticationProvider}, the responsibility for password
60 * validation is delegated to <code>PasswordAuthenticationDao</code>.
61 * </p>
62 *
63 * <p>
64 * Upon successful validation, a
65 * <code>UsernamePasswordAuthenticationToken</code> will be created and
66 * returned to the caller. The token will include as its principal either a
67 * <code>String</code> representation of the username, or the {@link
68 * UserDetails} that was returned from the authentication repository. Using
69 * <code>String</code> is appropriate if a container adapter is being used, as
70 * it expects <code>String</code> representations of the username. Using
71 * <code>UserDetails</code> is appropriate if you require access to additional
72 * properties of the authenticated user, such as email addresses,
73 * human-friendly names etc. As container adapters are not recommended to be
74 * used, and <code>UserDetails</code> implementations provide additional
75 * flexibility, by default a <code>UserDetails</code> is returned. To override
76 * this default, set the {@link #setForcePrincipalAsString} to
77 * <code>true</code>.
78 * </p>
79 *
80 * <p>
81 * Caching is handled via the <code>UserDetails</code> object being placed in
82 * the {@link UserCache}. This ensures that subsequent requests with the same
83 * username and password can be validated without needing to query the {@link
84 * PasswordAuthenticationDao}. It should be noted that if a user appears to
85 * present an incorrect password, the {@link PasswordAuthenticationDao} will
86 * be queried to confirm the most up-to-date password was used for comparison.
87 * </p>
88 *
89 * <p>
90 * If an application context is detected (which is automatically the case when
91 * the bean is started within a Spring container), application events will be
92 * published to the context. See {@link
93 * net.sf.acegisecurity.providers.dao.event.AuthenticationEvent} for further
94 * information.
95 * </p>
96 *
97 * @author Karel Miarka
98 */
99 public class PasswordDaoAuthenticationProvider implements AuthenticationProvider,
100 InitializingBean, ApplicationContextAware {
101 //~ Instance fields ========================================================
102
103 private ApplicationContext context;
104 private PasswordAuthenticationDao authenticationDao;
105 private UserCache userCache = new NullUserCache();
106 private boolean forcePrincipalAsString = false;
107
108 //~ Methods ================================================================
109
110 public void setApplicationContext(ApplicationContext applicationContext)
111 throws BeansException {
112 this.context = applicationContext;
113 }
114
115 public ApplicationContext getContext() {
116 return context;
117 }
118
119 public void setForcePrincipalAsString(boolean forcePrincipalAsString) {
120 this.forcePrincipalAsString = forcePrincipalAsString;
121 }
122
123 public boolean isForcePrincipalAsString() {
124 return forcePrincipalAsString;
125 }
126
127 public void setPasswordAuthenticationDao(
128 PasswordAuthenticationDao authenticationDao) {
129 this.authenticationDao = authenticationDao;
130 }
131
132 public PasswordAuthenticationDao getPasswordAuthenticationDao() {
133 return authenticationDao;
134 }
135
136 public void setUserCache(UserCache userCache) {
137 this.userCache = userCache;
138 }
139
140 public UserCache getUserCache() {
141 return userCache;
142 }
143
144 public void afterPropertiesSet() throws Exception {
145 Assert.notNull(this.authenticationDao, "A Password authentication DAO must be set");
146 Assert.notNull(this.userCache, "A user cache must be set");
147 }
148
149 public Authentication authenticate(Authentication authentication)
150 throws AuthenticationException {
151 // Determine username
152 String username = authentication.getPrincipal().toString();
153
154 if (authentication.getPrincipal() instanceof UserDetails) {
155 username = ((UserDetails) authentication.getPrincipal())
156 .getUsername();
157 }
158
159 String password = authentication.getCredentials().toString();
160
161 boolean cacheWasUsed = true;
162 UserDetails user = this.userCache.getUserFromCache(username);
163
164 // Check if the provided password is the same as the password in cache
165 if ((user != null) && !password.equals(user.getPassword())) {
166 user = null;
167 this.userCache.removeUserFromCache(username);
168 }
169
170 if (user == null) {
171 cacheWasUsed = false;
172
173 try {
174 user = getUserFromBackend(username, password);
175 } catch (BadCredentialsException ex) {
176 if (this.context != null) {
177 if ((username == null) || "".equals(username)) {
178 username = "NONE_PROVIDED";
179 }
180
181 context.publishEvent(new AuthenticationFailureUsernameOrPasswordEvent(
182 authentication,
183 new User(username, "*****", false, false, false,
184 false, new GrantedAuthority[0])));
185 }
186
187 throw ex;
188 }
189 }
190
191 if (!user.isEnabled()) {
192 if (this.context != null) {
193 context.publishEvent(new AuthenticationFailureDisabledEvent(
194 authentication, user));
195 }
196
197 throw new DisabledException("User is disabled");
198 }
199
200 if (!user.isAccountNonExpired()) {
201 if (this.context != null) {
202 context.publishEvent(new AuthenticationFailureAccountExpiredEvent(
203 authentication, user));
204 }
205
206 throw new AccountExpiredException("User account has expired");
207 }
208
209 if (!user.isAccountNonLocked()) {
210 if (this.context != null) {
211 context.publishEvent(new AuthenticationFailureAccountLockedEvent(
212 authentication, user));
213 }
214
215 throw new LockedException("User account is locked");
216 }
217
218 if (!user.isCredentialsNonExpired()) {
219 if (this.context != null) {
220 context.publishEvent(new AuthenticationFailureCredentialsExpiredEvent(
221 authentication, user));
222 }
223
224 throw new CredentialsExpiredException(
225 "User credentials have expired");
226 }
227
228 if (!cacheWasUsed) {
229 // Put into cache
230 this.userCache.putUserInCache(user);
231
232 // As this appears to be an initial login, publish the event
233 if (this.context != null) {
234 context.publishEvent(new AuthenticationSuccessEvent(
235 authentication, user));
236 }
237 }
238
239 Object principalToReturn = user;
240
241 if (forcePrincipalAsString) {
242 principalToReturn = user.getUsername();
243 }
244
245 return createSuccessAuthentication(principalToReturn, authentication,
246 user);
247 }
248
249 public boolean supports(Class authentication) {
250 if (UsernamePasswordAuthenticationToken.class.isAssignableFrom(
251 authentication)) {
252 return true;
253 } else {
254 return false;
255 }
256 }
257
258 /**
259 * Creates a successful {@link Authentication} object.
260 *
261 * <P>
262 * Protected so subclasses can override. This might be required if multiple
263 * credentials need to be placed into a custom <code>Authentication</code>
264 * object, such as a password as well as a ZIP code.
265 * </p>
266 *
267 * <P>
268 * Subclasses will usually store the original credentials the user supplied
269 * (not salted or encoded passwords) in the returned
270 * <code>Authentication</code> object.
271 * </p>
272 *
273 * @param principal that should be the principal in the returned object
274 * (defined by the {@link #isForcePrincipalAsString()} method)
275 * @param authentication that was presented to the
276 * <code>PasswordDaoAuthenticationProvider</code> for validation
277 * @param user that was loaded by the
278 * <code>PasswordAuthenticationDao</code>
279 *
280 * @return the successful authentication token
281 */
282 protected Authentication createSuccessAuthentication(Object principal,
283 Authentication authentication, UserDetails user) {
284 // Ensure we return the original credentials the user supplied,
285 // so subsequent attempts are successful even with encoded passwords.
286 // Also ensure we return the original getDetails(), so that future
287 // authentication events after cache expiry contain the details
288 UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal,
289 authentication.getCredentials(), user.getAuthorities());
290 result.setDetails((authentication.getDetails() != null)
291 ? authentication.getDetails() : null);
292
293 return result;
294 }
295
296 private UserDetails getUserFromBackend(String username, String password) {
297 try {
298 return this.authenticationDao.loadUserByUsernameAndPassword(username,
299 password);
300 } catch (DataAccessException repositoryProblem) {
301 throw new AuthenticationServiceException(repositoryProblem
302 .getMessage(), repositoryProblem);
303 }
304 }
305 }