diff --git a/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java b/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java index 33c2ff0674..60c8cca5fa 100644 --- a/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java +++ b/web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java @@ -377,7 +377,6 @@ public final class ObservationFilterChainDecorator implements FilterChainProxy.F private void error(Throwable error) { if (this.state.get() == 1) { - this.scope.close(); this.scope.getCurrentObservation().error(error); } } diff --git a/web/src/test/java/org/springframework/security/web/ObservationFilterChainDecoratorTests.java b/web/src/test/java/org/springframework/security/web/ObservationFilterChainDecoratorTests.java index 5e8c2a47d5..a8a1655c43 100644 --- a/web/src/test/java/org/springframework/security/web/ObservationFilterChainDecoratorTests.java +++ b/web/src/test/java/org/springframework/security/web/ObservationFilterChainDecoratorTests.java @@ -34,8 +34,10 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -109,6 +111,23 @@ public class ObservationFilterChainDecoratorTests { assertThat(events.get(1).getName()).isEqualTo("authentication.basic.after"); } + // gh-12787 + @Test + void decorateFiltersWhenErrorsThenClosesObservationOnlyOnce() throws Exception { + ObservationHandler handler = mock(ObservationHandler.class); + given(handler.supportsContext(any())).willReturn(true); + ObservationRegistry registry = ObservationRegistry.create(); + registry.observationConfig().observationHandler(handler); + ObservationFilterChainDecorator decorator = new ObservationFilterChainDecorator(registry); + FilterChain chain = mock(FilterChain.class); + Filter filter = mock(Filter.class); + willThrow(IllegalArgumentException.class).given(filter).doFilter(any(), any(), any()); + FilterChain decorated = decorator.decorate(chain, List.of(filter)); + assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy( + () -> decorated.doFilter(new MockHttpServletRequest("GET", "/"), new MockHttpServletResponse())); + verify(handler).onScopeClosed(any()); + } + private static class BasicAuthenticationFilter implements Filter { @Override