diff --git a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java index 090f8b2605..6d5f2af936 100644 --- a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java +++ b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java @@ -156,6 +156,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar throws AuthenticationException { Class toTest = authentication.getClass(); AuthenticationException lastException = null; + AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); @@ -205,7 +206,7 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar // handled the request } catch (AuthenticationException e) { - lastException = e; + lastException = parentException = e; } } @@ -234,7 +235,11 @@ public class ProviderManager implements AuthenticationManager, MessageSourceAwar "No AuthenticationProvider found for {0}")); } - prepareException(lastException, authentication); + // If the parent AuthenticationManager was attempted and failed than it will publish an AbstractAuthenticationFailureEvent + // This check prevents a duplicate AbstractAuthenticationFailureEvent if the parent AuthenticationManager already published it + if (parentException == null) { + prepareException(lastException, authentication); + } throw lastException; } diff --git a/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java b/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java index aca4248827..7cc4348b11 100644 --- a/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java +++ b/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java @@ -16,18 +16,20 @@ package org.springframework.security.authentication; -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import org.junit.Test; import org.springframework.context.MessageSource; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.*; + /** * Tests {@link ProviderManager}. * @@ -257,7 +259,6 @@ public class ProviderManagerTests { catch (BadCredentialsException e) { assertThat(e).isSameAs(expected); } - verify(publisher).publishAuthenticationFailure(expected, authReq); } @Test @@ -298,6 +299,32 @@ public class ProviderManagerTests { } } + // gh-6281 + @Test + public void authenticateWhenFailsInParentAndPublishesThenChildDoesNotPublish() { + BadCredentialsException badCredentialsExParent = new BadCredentialsException("Bad Credentials in parent"); + ProviderManager parentMgr = new ProviderManager( + Collections.singletonList(createProviderWhichThrows(badCredentialsExParent))); + ProviderManager childMgr = new ProviderManager(Collections.singletonList(createProviderWhichThrows( + new BadCredentialsException("Bad Credentials in child"))), parentMgr); + + AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class); + parentMgr.setAuthenticationEventPublisher(publisher); + childMgr.setAuthenticationEventPublisher(publisher); + + final Authentication authReq = mock(Authentication.class); + + try { + childMgr.authenticate(authReq); + fail("Expected exception"); + } + catch (BadCredentialsException e) { + assertThat(e).isSameAs(badCredentialsExParent); + } + verify(publisher).publishAuthenticationFailure(badCredentialsExParent, authReq); // Parent publishes + verifyNoMoreInteractions(publisher); // Child should not publish (duplicate event) + } + private AuthenticationProvider createProviderWhichThrows( final AuthenticationException e) { AuthenticationProvider provider = mock(AuthenticationProvider.class);