View Javadoc

    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;
    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     //~ Static fields/initializers =============================================
    106
    107     private static final Log logger = LogFactory.getLog(ProviderManager.class);
    108
    109     //~ Instance fields ========================================================
    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     //~ Methods ================================================================
    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         // Publish the event
    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         // Throw the exception
    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 }