1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.acegisecurity.providers;
17
18 import java.lang.reflect.Constructor;
19 import java.lang.reflect.InvocationTargetException;
20 import java.util.Iterator;
21 import java.util.List;
22 import java.util.Properties;
23
24 import org.acegisecurity.AbstractAuthenticationManager;
25 import org.acegisecurity.AccountExpiredException;
26 import org.acegisecurity.AcegiMessageSource;
27 import org.acegisecurity.Authentication;
28 import org.acegisecurity.AuthenticationException;
29 import org.acegisecurity.AuthenticationServiceException;
30 import org.acegisecurity.BadCredentialsException;
31 import org.acegisecurity.CredentialsExpiredException;
32 import org.acegisecurity.DisabledException;
33 import org.acegisecurity.LockedException;
34 import org.acegisecurity.concurrent.ConcurrentLoginException;
35 import org.acegisecurity.concurrent.ConcurrentSessionController;
36 import org.acegisecurity.concurrent.NullConcurrentSessionController;
37 import org.acegisecurity.event.authentication.AbstractAuthenticationEvent;
38 import org.acegisecurity.event.authentication.AuthenticationFailureBadCredentialsEvent;
39 import org.acegisecurity.event.authentication.AuthenticationFailureConcurrentLoginEvent;
40 import org.acegisecurity.event.authentication.AuthenticationFailureCredentialsExpiredEvent;
41 import org.acegisecurity.event.authentication.AuthenticationFailureDisabledEvent;
42 import org.acegisecurity.event.authentication.AuthenticationFailureExpiredEvent;
43 import org.acegisecurity.event.authentication.AuthenticationFailureLockedEvent;
44 import org.acegisecurity.event.authentication.AuthenticationFailureProviderNotFoundEvent;
45 import org.acegisecurity.event.authentication.AuthenticationFailureProxyUntrustedEvent;
46 import org.acegisecurity.event.authentication.AuthenticationFailureServiceExceptionEvent;
47 import org.acegisecurity.event.authentication.AuthenticationSuccessEvent;
48 import org.acegisecurity.providers.cas.ProxyUntrustedException;
49 import org.acegisecurity.userdetails.UsernameNotFoundException;
50 import org.apache.commons.logging.Log;
51 import org.apache.commons.logging.LogFactory;
52 import org.springframework.beans.factory.InitializingBean;
53 import org.springframework.context.ApplicationEventPublisher;
54 import org.springframework.context.ApplicationEventPublisherAware;
55 import org.springframework.context.MessageSource;
56 import org.springframework.context.MessageSourceAware;
57 import org.springframework.context.support.MessageSourceAccessor;
58 import org.springframework.util.Assert;
59
60
61 /***
62 * Iterates an {@link Authentication} request through a list of {@link
63 * AuthenticationProvider}s. Can optionally be configured with a {@link
64 * ConcurrentSessionController} to limit the number of sessions a user can
65 * have.
66 *
67 * <p>
68 * <code>AuthenticationProvider</code>s are tried in order until one provides a
69 * non-null response. A non-null response indicates the provider had authority
70 * to decide on the authentication request and no further providers are tried.
71 * If an <code>AuthenticationException</code> is thrown by a provider, it is
72 * retained until subsequent providers are tried. If a subsequent provider
73 * successfully authenticates the request, the earlier authentication
74 * exception is disregarded and the successful authentication will be used. If
75 * no subsequent provider provides a non-null response, or a new
76 * <code>AuthenticationException</code>, the last
77 * <code>AuthenticationException</code> received will be used. If no provider
78 * returns a non-null response, or indicates it can even process an
79 * <code>Authentication</code>, the <code>ProviderManager</code> will throw a
80 * <code>ProviderNotFoundException</code>.
81 * </p>
82 *
83 * <p>
84 * If a valid <code>Authentication</code> is returned by an
85 * <code>AuthenticationProvider</code>, the <code>ProviderManager</code> will
86 * publish an {@link
87 * org.acegisecurity.event.authentication.AuthenticationSuccessEvent}. If an
88 * <code>AuthenticationException</code> is detected, the final
89 * <code>AuthenticationException</code> thrown will be used to publish an
90 * appropriate failure event. By default <code>ProviderManager</code> maps
91 * common exceptions to events, but this can be fine-tuned by providing a new
92 * <code>exceptionMappings</code><code>java.util.Properties</code> object. In
93 * the properties object, each of the keys represent the fully qualified
94 * classname of the exception, and each of the values represent the name of an
95 * event class which subclasses {@link
96 * org.acegisecurity.event.authentication.AbstractAuthenticationFailureEvent}
97 * and provides its constructor.
98 * </p>
99 *
100 * @see ConcurrentSessionController
101 */
102 public class ProviderManager extends AbstractAuthenticationManager
103 implements InitializingBean, ApplicationEventPublisherAware,
104 MessageSourceAware {
105
106
107 private static final Log logger = LogFactory.getLog(ProviderManager.class);
108
109
110
111 private ApplicationEventPublisher applicationEventPublisher;
112 private ConcurrentSessionController sessionController = new NullConcurrentSessionController();
113 private List providers;
114 protected MessageSourceAccessor messages = AcegiMessageSource.getAccessor();
115 private Properties exceptionMappings;
116
117
118
119 public void afterPropertiesSet() throws Exception {
120 checkIfValidList(this.providers);
121 Assert.notNull(this.messages, "A message source must be set");
122
123 if (exceptionMappings == null) {
124 exceptionMappings = new Properties();
125 exceptionMappings.put(AccountExpiredException.class.getName(),
126 AuthenticationFailureExpiredEvent.class.getName());
127 exceptionMappings.put(AuthenticationServiceException.class.getName(),
128 AuthenticationFailureServiceExceptionEvent.class.getName());
129 exceptionMappings.put(LockedException.class.getName(),
130 AuthenticationFailureLockedEvent.class.getName());
131 exceptionMappings.put(CredentialsExpiredException.class.getName(),
132 AuthenticationFailureCredentialsExpiredEvent.class.getName());
133 exceptionMappings.put(DisabledException.class.getName(),
134 AuthenticationFailureDisabledEvent.class.getName());
135 exceptionMappings.put(BadCredentialsException.class.getName(),
136 AuthenticationFailureBadCredentialsEvent.class.getName());
137 exceptionMappings.put(UsernameNotFoundException.class.getName(),
138 AuthenticationFailureBadCredentialsEvent.class.getName());
139 exceptionMappings.put(ConcurrentLoginException.class.getName(),
140 AuthenticationFailureConcurrentLoginEvent.class.getName());
141 exceptionMappings.put(ProviderNotFoundException.class.getName(),
142 AuthenticationFailureProviderNotFoundEvent.class.getName());
143 exceptionMappings.put(ProxyUntrustedException.class.getName(),
144 AuthenticationFailureProxyUntrustedEvent.class.getName());
145 doAddExtraDefaultExceptionMappings(exceptionMappings);
146 }
147 }
148
149 private void checkIfValidList(List listToCheck) {
150 if ((listToCheck == null) || (listToCheck.size() == 0)) {
151 throw new IllegalArgumentException(
152 "A list of AuthenticationManagers is required");
153 }
154 }
155
156 /***
157 * Provided so subclasses can add extra exception mappings during startup
158 * if no exception mappings are injected by the IoC container.
159 *
160 * @param exceptionMappings the properties object, which already has
161 * entries in it
162 */
163 protected void doAddExtraDefaultExceptionMappings(
164 Properties exceptionMappings) {}
165
166 /***
167 * Attempts to authenticate the passed {@link Authentication} object.
168 *
169 * <p>
170 * The list of {@link AuthenticationProvider}s will be successively tried
171 * until an <code>AuthenticationProvider</code> indicates it is capable
172 * of authenticating the type of <code>Authentication</code> object
173 * passed. Authentication will then be attempted with that
174 * <code>AuthenticationProvider</code>.
175 * </p>
176 *
177 * <p>
178 * If more than one <code>AuthenticationProvider</code> supports the passed
179 * <code>Authentication</code> object, only the first
180 * <code>AuthenticationProvider</code> tried will determine the result. No
181 * subsequent <code>AuthenticationProvider</code>s will be tried.
182 * </p>
183 *
184 * @param authentication the authentication request object.
185 *
186 * @return a fully authenticated object including credentials.
187 *
188 * @throws AuthenticationException if authentication fails.
189 */
190 public Authentication doAuthentication(Authentication authentication)
191 throws AuthenticationException {
192 Iterator iter = providers.iterator();
193
194 Class toTest = authentication.getClass();
195
196 AuthenticationException lastException = null;
197
198 while (iter.hasNext()) {
199 AuthenticationProvider provider = (AuthenticationProvider) iter.next();
200
201 if (provider.supports(toTest)) {
202 logger.debug("Authentication attempt using "
203 + provider.getClass().getName());
204
205 Authentication result = null;
206
207 try {
208 result = provider.authenticate(authentication);
209 sessionController.checkAuthenticationAllowed(result);
210 } catch (AuthenticationException ae) {
211 lastException = ae;
212 result = null;
213 }
214
215 if (result != null) {
216 sessionController.registerSuccessfulAuthentication(result);
217 applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(
218 result));
219
220 return result;
221 }
222 }
223 }
224
225 if (lastException == null) {
226 lastException = new ProviderNotFoundException(messages.getMessage(
227 "ProviderManager.providerNotFound",
228 new Object[] {toTest.getName()},
229 "No AuthenticationProvider found for {0}"));
230 }
231
232
233 String className = exceptionMappings.getProperty(lastException.getClass()
234 .getName());
235 AbstractAuthenticationEvent event = null;
236
237 if (className != null) {
238 try {
239 Class clazz = getClass().getClassLoader().loadClass(className);
240 Constructor constructor = clazz.getConstructor(new Class[] {Authentication.class, AuthenticationException.class});
241 Object obj = constructor.newInstance(new Object[] {authentication, lastException});
242 Assert.isInstanceOf(AbstractAuthenticationEvent.class, obj,
243 "Must be an AbstractAuthenticationEvent");
244 event = (AbstractAuthenticationEvent) obj;
245 } catch (ClassNotFoundException ignored) {}
246 catch (NoSuchMethodException ignored) {}
247 catch (IllegalAccessException ignored) {}
248 catch (InstantiationException ignored) {}
249 catch (InvocationTargetException ignored) {}
250 }
251
252 if (event != null) {
253 applicationEventPublisher.publishEvent(event);
254 } else {
255 if (logger.isDebugEnabled()) {
256 logger.debug("No event was found for the exception "
257 + lastException.getClass().getName());
258 }
259 }
260
261
262 throw lastException;
263 }
264
265 public List getProviders() {
266 return this.providers;
267 }
268
269 /***
270 * The configured {@link ConcurrentSessionController} is returned or the
271 * {@link NullConcurrentSessionController} if a specific one has not been
272 * set.
273 *
274 * @return {@link ConcurrentSessionController} instance
275 */
276 public ConcurrentSessionController getSessionController() {
277 return sessionController;
278 }
279
280 public void setApplicationEventPublisher(
281 ApplicationEventPublisher applicationEventPublisher) {
282 this.applicationEventPublisher = applicationEventPublisher;
283 }
284
285 public void setMessageSource(MessageSource messageSource) {
286 this.messages = new MessageSourceAccessor(messageSource);
287 }
288
289 /***
290 * Sets the {@link AuthenticationProvider} objects to be used for
291 * authentication.
292 *
293 * @param newList
294 *
295 * @throws IllegalArgumentException DOCUMENT ME!
296 */
297 public void setProviders(List newList) {
298 checkIfValidList(newList);
299
300 Iterator iter = newList.iterator();
301
302 while (iter.hasNext()) {
303 Object currentObject = null;
304
305 try {
306 currentObject = iter.next();
307
308 AuthenticationProvider attemptToCast = (AuthenticationProvider) currentObject;
309 } catch (ClassCastException cce) {
310 throw new IllegalArgumentException("AuthenticationProvider "
311 + currentObject.getClass().getName()
312 + " must implement AuthenticationProvider");
313 }
314 }
315
316 this.providers = newList;
317 }
318
319 /***
320 * Set the {@link ConcurrentSessionController} to be used for limiting
321 * user's sessions. The {@link NullConcurrentSessionController} is used
322 * by default
323 *
324 * @param sessionController {@link ConcurrentSessionController}
325 */
326 public void setSessionController(
327 ConcurrentSessionController sessionController) {
328 this.sessionController = sessionController;
329 }
330 }