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 21b2a291f0..937dea98f0 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); @@ -117,6 +118,13 @@ public class DefaultAuthenticationEventPublisher implements AuthenticationEventP 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) { @@ -163,6 +171,26 @@ public class DefaultAuthenticationEventPublisher implements AuthenticationEventP } } + /** + * 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 ae84a545e8..4750ee5149 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.AuthenticationFailureLo 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 class DefaultAuthenticationEventPublisherTests { 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) {