Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

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 }