diff --git a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java index 30392eafc3..943e46dce3 100644 --- a/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/ObservationAuthorizationManager.java @@ -22,8 +22,12 @@ import io.micrometer.observation.Observation; import io.micrometer.observation.ObservationConvention; import io.micrometer.observation.ObservationRegistry; +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.Authentication; +import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.util.Assert; /** @@ -32,7 +36,7 @@ import org.springframework.util.Assert; * @author Josh Cummings * @since 6.0 */ -public final class ObservationAuthorizationManager implements AuthorizationManager { +public final class ObservationAuthorizationManager implements AuthorizationManager, MessageSourceAware { private final ObservationRegistry registry; @@ -40,6 +44,8 @@ public final class ObservationAuthorizationManager implements AuthorizationMa private ObservationConvention> convention = new AuthorizationObservationConvention(); + private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + public ObservationAuthorizationManager(ObservationRegistry registry, AuthorizationManager delegate) { this.registry = registry; this.delegate = delegate; @@ -57,7 +63,8 @@ public final class ObservationAuthorizationManager implements AuthorizationMa AuthorizationDecision decision = this.delegate.check(wrapped, object); context.setDecision(decision); if (decision != null && !decision.isGranted()) { - observation.error(new AccessDeniedException("Access Denied")); + observation.error(new AccessDeniedException( + this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access Denied"))); } return decision; } @@ -81,4 +88,14 @@ public final class ObservationAuthorizationManager implements AuthorizationMa this.convention = convention; } + /** + * Set the MessageSource that this object runs in. + * @param messageSource The message source to be used by this object + * @since 6.2 + */ + @Override + public void setMessageSource(final MessageSource messageSource) { + this.messages = new MessageSourceAccessor(messageSource); + } + } diff --git a/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java index f3cd921aed..5f14b8ffa2 100644 --- a/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/ObservationAuthorizationManagerTests.java @@ -16,6 +16,7 @@ package org.springframework.security.authorization; +import java.util.Optional; import java.util.function.Supplier; import io.micrometer.observation.Observation; @@ -25,6 +26,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.springframework.context.MessageSource; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.Authentication; @@ -32,6 +34,7 @@ import org.springframework.security.core.Authentication; 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.ArgumentMatchers.eq; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -85,14 +88,20 @@ public class ObservationAuthorizationManagerTests { @Test void verifyWhenErrorsThenObserves() { + MessageSource source = mock(MessageSource.class); + this.tested.setMessageSource(source); given(this.handler.supportsContext(any())).willReturn(true); given(this.authorizationManager.check(any(), any())).willReturn(this.deny); + given(source.getMessage(eq("AbstractAccessDecisionManager.accessDenied"), any(), any(), any())) + .willReturn("accessDenied"); assertThatExceptionOfType(AccessDeniedException.class) .isThrownBy(() -> this.tested.verify(this.token, this.object)); ArgumentCaptor captor = ArgumentCaptor.forClass(Observation.Context.class); verify(this.handler).onStart(captor.capture()); assertThat(captor.getValue().getName()).isEqualTo(AuthorizationObservationConvention.OBSERVATION_NAME); assertThat(captor.getValue().getError()).isInstanceOf(AccessDeniedException.class); + assertThat(Optional.ofNullable(captor.getValue().getError()).map(Throwable::getMessage).orElse("")) + .isEqualTo("accessDenied"); assertThat(captor.getValue()).isInstanceOf(AuthorizationObservationContext.class); AuthorizationObservationContext context = (AuthorizationObservationContext) captor.getValue(); assertThat(context.getAuthentication()).isNull();