diff --git a/core/src/main/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisher.java b/core/src/main/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisher.java index 2d0015fc55e..6a92b3f0e62 100644 --- a/core/src/main/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisher.java +++ b/core/src/main/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisher.java @@ -64,6 +64,7 @@ public class DefaultAuthenticationEventPublisher implements AuthenticationEventP private ApplicationEventPublisher applicationEventPublisher; private final HashMap> exceptionMappings = new HashMap<>(); + private Constructor defaultAuthenticationFailureEventConstructor; public DefaultAuthenticationEventPublisher() { this(null); @@ -114,6 +115,13 @@ public void publishAuthenticationFailure(AuthenticationException exception, catch (IllegalAccessException | InvocationTargetException | InstantiationException ignored) { } } + else if (defaultAuthenticationFailureEventConstructor != null) { + try { + event = defaultAuthenticationFailureEventConstructor.newInstance(authentication, exception); + } + catch (IllegalAccessException | InvocationTargetException | InstantiationException ignored) { + } + } if (event != null) { if (applicationEventPublisher != null) { @@ -160,6 +168,26 @@ public void setAdditionalExceptionMappings(Properties additionalExceptionMapping } } + /** + * 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, + "The defaultAuthenticationFailureEventClass must not be null"); + try { + this.defaultAuthenticationFailureEventConstructor = defaultAuthenticationFailureEventClass + .getConstructor(Authentication.class, AuthenticationException.class); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Default Authentication Failure event class " + + defaultAuthenticationFailureEventClass.getName() + " has no suitable constructor"); + } + } + private void addMapping(String exceptionClass, Class eventClass) { try { diff --git a/core/src/test/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisherTests.java b/core/src/test/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisherTests.java index ae84a545e80..4750ee51496 100644 --- a/core/src/test/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisherTests.java +++ b/core/src/test/java/org/springframework/security/authentication/DefaultAuthenticationEventPublisherTests.java @@ -27,6 +27,7 @@ import org.springframework.security.authentication.event.AuthenticationFailureProviderNotFoundEvent; import org.springframework.security.authentication.event.AuthenticationFailureServiceExceptionEvent; import org.springframework.security.authentication.event.AuthenticationSuccessEvent; +import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -137,6 +138,37 @@ public void unknownFailureExceptionIsIgnored() { verifyZeroInteractions(appPublisher); } + @Test(expected = IllegalArgumentException.class) + public void defaultAuthenticationFailureEventClassSetNullThen() { + publisher = new DefaultAuthenticationEventPublisher(); + publisher.setDefaultAuthenticationFailureEvent(null); + } + + @Test + public void defaultAuthenticationFailureEventIsPublished() { + publisher = new DefaultAuthenticationEventPublisher(); + publisher.setDefaultAuthenticationFailureEvent(AuthenticationFailureBadCredentialsEvent.class); + ApplicationEventPublisher appPublisher = mock(ApplicationEventPublisher.class); + + publisher.setApplicationEventPublisher(appPublisher); + publisher.publishAuthenticationFailure(new AuthenticationException("") { + }, mock(Authentication.class)); + verify(appPublisher).publishEvent(isA(AuthenticationFailureBadCredentialsEvent.class)); + } + + @Test(expected = RuntimeException.class) + public void defaultAuthenticationFailureEventMissingAppropriateConstructorThen() { + publisher = new DefaultAuthenticationEventPublisher(); + publisher.setDefaultAuthenticationFailureEvent(AuthenticationFailureEventWithoutAppropriateConstructor.class); + } + + private static final class AuthenticationFailureEventWithoutAppropriateConstructor extends + AbstractAuthenticationFailureEvent { + AuthenticationFailureEventWithoutAppropriateConstructor(Authentication auth) { + super(auth, new AuthenticationException("") {}); + } + } + private static final class MockAuthenticationException extends AuthenticationException { MockAuthenticationException(String msg) {