org.springframework.security.authentication.DefaultAuthenticationEventPublisher Maven / Gradle / Ivy
/*
* Copyright 2002-2020 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
*
* https://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.HashMap;
import java.util.Map;
import java.util.Properties;
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.
*
* Maps well-known AuthenticationException types to events and publishes them via
* the application context. If configured as a bean, it will pick up the
* ApplicationEventPublisher automatically. Otherwise, the constructor which
* takes the publisher as an argument should be used.
*
* The exception-mapping system can be fine-tuned by setting the
* additionalExceptionMappings as a java.util.Properties
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 additionalExceptionMappings 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> exceptionMappings = new HashMap<>();
private Constructor defaultAuthenticationFailureEventConstructor;
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);
addMapping("org.springframework.security.oauth2.server.resource.InvalidBearerTokenException",
AuthenticationFailureBadCredentialsEvent.class);
}
@Override
public void publishAuthenticationSuccess(Authentication authentication) {
if (this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(new AuthenticationSuccessEvent(authentication));
}
}
@Override
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
Constructor constructor = getEventConstructor(exception);
AbstractAuthenticationEvent event = null;
if (constructor != null) {
try {
event = constructor.newInstance(authentication, exception);
}
catch (IllegalAccessException | InvocationTargetException | InstantiationException ignored) {
}
}
if (event != null) {
if (this.applicationEventPublisher != null) {
this.applicationEventPublisher.publishEvent(event);
}
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug("No event was found for the exception " + exception.getClass().getName());
}
}
}
private Constructor getEventConstructor(AuthenticationException exception) {
Constructor eventConstructor = this.exceptionMappings
.get(exception.getClass().getName());
return (eventConstructor != null) ? eventConstructor : this.defaultAuthenticationFailureEventConstructor;
}
@Override
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 ProviderManager
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.
* @deprecated use {@link #setAdditionalExceptionMappings(Map)}
*/
@Deprecated
@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) clazz);
}
catch (ClassNotFoundException ex) {
throw new RuntimeException("Failed to load authentication event class " + eventClass);
}
}
}
/**
* Sets additional exception to event mappings. These are automatically merged with
* the default exception to event mappings that ProviderManager
defines.
* @param mappings where keys are exception classes and values are event classes.
* @since 5.3
*/
public void setAdditionalExceptionMappings(
Map, Class> mappings) {
Assert.notEmpty(mappings, "The mappings Map must not be empty nor null");
for (Map.Entry, Class> entry : mappings
.entrySet()) {
Class exceptionClass = entry.getKey();
Class eventClass = entry.getValue();
Assert.notNull(exceptionClass, "exceptionClass cannot be null");
Assert.notNull(eventClass, "eventClass cannot be null");
addMapping(exceptionClass.getName(), (Class) eventClass);
}
}
/**
* Sets a default authentication failure event as a fallback event for any unmapped
* exceptions not mapped in the exception mappings.
* @param defaultAuthenticationFailureEventClass is the authentication failure event
* class to be fired for unmapped exceptions.
*/
public void setDefaultAuthenticationFailureEvent(
Class defaultAuthenticationFailureEventClass) {
Assert.notNull(defaultAuthenticationFailureEventClass,
"defaultAuthenticationFailureEventClass must not be null");
try {
this.defaultAuthenticationFailureEventConstructor = defaultAuthenticationFailureEventClass
.getConstructor(Authentication.class, AuthenticationException.class);
}
catch (NoSuchMethodException ex) {
throw new RuntimeException("Default Authentication Failure event class "
+ defaultAuthenticationFailureEventClass.getName() + " has no suitable constructor");
}
}
private void addMapping(String exceptionClass, Class eventClass) {
try {
Constructor constructor = eventClass
.getConstructor(Authentication.class, AuthenticationException.class);
this.exceptionMappings.put(exceptionClass, constructor);
}
catch (NoSuchMethodException ex) {
throw new RuntimeException(
"Authentication event class " + eventClass.getName() + " has no suitable constructor");
}
}
}