/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.security.authentication; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.security.authentication.event.AbstractAuthenticationEvent; import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; import org.springframework.security.authentication.event.AuthenticationFailureBadCredentialsEvent; import org.springframework.security.authentication.event.AuthenticationFailureCredentialsExpiredEvent; import org.springframework.security.authentication.event.AuthenticationFailureDisabledEvent; import org.springframework.security.authentication.event.AuthenticationFailureExpiredEvent; import org.springframework.security.authentication.event.AuthenticationFailureLockedEvent; import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; import org.springframework.security.authentication.event.AuthenticationFailureProxyUntrustedEvent; import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.util.Assert; /** * The default strategy for publishing authentication events. * <p> * Maps well-known <tt>AuthenticationException</tt> types to events and publishes them via * the application context. If configured as a bean, it will pick up the * <tt>ApplicationEventPublisher</tt> automatically. Otherwise, the constructor which * takes the publisher as an argument should be used. * <p> * The exception-mapping system can be fine-tuned by setting the * <tt>additionalExceptionMappings</tt> as a <code>java.util.Properties</code> object. In * the properties object, each of the keys represent the fully qualified classname of the * exception, and each of the values represent the name of an event class which subclasses * {@link org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent} * and provides its constructor. The <tt>additionalExceptionMappings</tt> will be merged * with the default ones. * * @author Luke Taylor * @since 3.0 */ public class DefaultAuthenticationEventPublisher implements AuthenticationEventPublisher, ApplicationEventPublisherAware { private final Log logger = LogFactory.getLog(getClass()); private ApplicationEventPublisher applicationEventPublisher; private final HashMap<String, Constructor<? extends AbstractAuthenticationEvent>> exceptionMappings = new HashMap<String, Constructor<? extends AbstractAuthenticationEvent>>(); public DefaultAuthenticationEventPublisher() { this(null); } public DefaultAuthenticationEventPublisher( ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; addMapping(BadCredentialsException.class.getName(), AuthenticationFailureBadCredentialsEvent.class); addMapping(UsernameNotFoundException.class.getName(), AuthenticationFailureBadCredentialsEvent.class); addMapping(AccountExpiredException.class.getName(), AuthenticationFailureExpiredEvent.class); addMapping(ProviderNotFoundException.class.getName(), AuthenticationFailureProviderNotFoundEvent.class); addMapping(DisabledException.class.getName(), AuthenticationFailureDisabledEvent.class); addMapping(LockedException.class.getName(), AuthenticationFailureLockedEvent.class); addMapping(AuthenticationServiceException.class.getName(), AuthenticationFailureServiceExceptionEvent.class); addMapping(CredentialsExpiredException.class.getName(), AuthenticationFailureCredentialsExpiredEvent.class); addMapping( "org.springframework.security.authentication.cas.ProxyUntrustedException", AuthenticationFailureProxyUntrustedEvent.class); } public void publishAuthenticationSuccess(Authentication authentication) { if (applicationEventPublisher != null) { applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent( authentication)); } } public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) { Constructor<? extends AbstractAuthenticationEvent> constructor = exceptionMappings .get(exception.getClass().getName()); AbstractAuthenticationEvent event = null; if (constructor != null) { try { event = constructor.newInstance(authentication, exception); } catch (IllegalAccessException ignored) { } catch (InstantiationException ignored) { } catch (InvocationTargetException ignored) { } } if (event != null) { if (applicationEventPublisher != null) { applicationEventPublisher.publishEvent(event); } } else { if (logger.isDebugEnabled()) { logger.debug("No event was found for the exception " + exception.getClass().getName()); } } } public void setApplicationEventPublisher( ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; } /** * Sets additional exception to event mappings. These are automatically merged with * the default exception to event mappings that <code>ProviderManager</code> defines. * * @param additionalExceptionMappings where keys are the fully-qualified string name * of the exception class and the values are the fully-qualified string name of the * event class to fire. */ @SuppressWarnings({ "unchecked" }) public void setAdditionalExceptionMappings(Properties additionalExceptionMappings) { Assert.notNull(additionalExceptionMappings, "The exceptionMappings object must not be null"); for (Object exceptionClass : additionalExceptionMappings.keySet()) { String eventClass = (String) additionalExceptionMappings.get(exceptionClass); try { Class<?> clazz = getClass().getClassLoader().loadClass(eventClass); Assert.isAssignable(AbstractAuthenticationFailureEvent.class, clazz); addMapping((String) exceptionClass, (Class<? extends AbstractAuthenticationFailureEvent>) clazz); } catch (ClassNotFoundException e) { throw new RuntimeException("Failed to load authentication event class " + eventClass); } } } private void addMapping(String exceptionClass, Class<? extends AbstractAuthenticationFailureEvent> eventClass) { try { Constructor<? extends AbstractAuthenticationEvent> constructor = eventClass .getConstructor(Authentication.class, AuthenticationException.class); exceptionMappings.put(exceptionClass, constructor); } catch (NoSuchMethodException e) { throw new RuntimeException("Authentication event class " + eventClass.getName() + " has no suitable constructor"); } } }