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 5cbd8fb300b..c144e38844e 100644 --- a/core/src/main/java/org/springframework/security/authentication/ProviderManager.java +++ b/core/src/main/java/org/springframework/security/authentication/ProviderManager.java @@ -244,15 +244,21 @@ public Authentication authenticate(Authentication authentication) throws Authent } // Parent was null, or didn't authenticate (or throw an exception). + boolean providerNotFound = false; if (lastException == null) { lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}")); + // This exception is synthesized because no provider here supports the + // token. In a parent/child hierarchy the manager that actually handled + // the request publishes the failure event, so publishing this one would + // emit a spurious event that masks the real AuthenticationException. + providerNotFound = true; } // If the parent AuthenticationManager was attempted and failed then it will // publish an AbstractAuthenticationFailureEvent // This check prevents a duplicate AbstractAuthenticationFailureEvent if the // parent AuthenticationManager already published it - if (parentException == null) { + if (parentException == null && !providerNotFound) { prepareException(lastException, authentication); } 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 4c66bf5bae4..48b35ce6675 100644 --- a/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java +++ b/core/src/test/java/org/springframework/security/authentication/ProviderManagerTests.java @@ -328,6 +328,28 @@ void authenticateWhenFailsInParentAndPublishesThenChildDoesNotPublish() { verifyNoMoreInteractions(publisher); // Child should not publish (duplicate event) } + // gh-19434 + @Test + void providerNotFoundFromParentPublishesOnlyChildException() { + ProviderManager parentMgr = new ProviderManager(List.of(createProviderWhichDoesNotSupport())); + BadCredentialsException childEx = new BadCredentialsException("Bad Credentials in child"); + ProviderManager childMgr = new ProviderManager(List.of(createProviderWhichThrows(childEx)), parentMgr); + AuthenticationEventPublisher publisher = mock(AuthenticationEventPublisher.class); + parentMgr.setAuthenticationEventPublisher(publisher); + childMgr.setAuthenticationEventPublisher(publisher); + Authentication authReq = mock(Authentication.class); + assertThatExceptionOfType(BadCredentialsException.class).isThrownBy(() -> childMgr.authenticate(authReq)) + .isSameAs(childEx); + verify(publisher).publishAuthenticationFailure(childEx, authReq); + verifyNoMoreInteractions(publisher); + } + + private AuthenticationProvider createProviderWhichDoesNotSupport() { + AuthenticationProvider provider = mock(AuthenticationProvider.class); + given(provider.supports(any(Class.class))).willReturn(false); + return provider; + } + private AuthenticationProvider createProviderWhichThrows(final AuthenticationException ex) { AuthenticationProvider provider = mock(AuthenticationProvider.class); given(provider.supports(any(Class.class))).willReturn(true);