From de034f7ad097630706c737ef8748e423de5c6996 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Tue, 18 Mar 2025 13:12:59 +0300 Subject: [PATCH 1/2] Add support ResolvableTypeProvider to authorization events Closes gh-16700 Signed-off-by: Max Batischev --- .../event/AuthorizationDeniedEvent.java | 14 +++++++++++++- .../event/AuthorizationGrantedEvent.java | 14 +++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java index 05d0fcdbc5d..7121f7e25af 100644 --- a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java +++ b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationDeniedEvent.java @@ -19,6 +19,8 @@ import java.util.function.Supplier; import org.springframework.context.ApplicationEvent; +import org.springframework.core.ResolvableType; +import org.springframework.core.ResolvableTypeProvider; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; @@ -31,7 +33,7 @@ * @since 5.7 */ @SuppressWarnings("serial") -public class AuthorizationDeniedEvent extends AuthorizationEvent { +public class AuthorizationDeniedEvent extends AuthorizationEvent implements ResolvableTypeProvider { /** * @deprecated Please use an {@link AuthorizationResult} constructor instead @@ -59,4 +61,14 @@ public T getObject() { return (T) getSource(); } + /** + * Get {@link ResolvableType} of this class. + * @return {@link ResolvableType} + * @since 6.5 + */ + @Override + public ResolvableType getResolvableType() { + return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getObject())); + } + } diff --git a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java index 9cde3519303..26115a51002 100644 --- a/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java +++ b/core/src/main/java/org/springframework/security/authorization/event/AuthorizationGrantedEvent.java @@ -20,6 +20,8 @@ import java.util.function.Supplier; import org.springframework.context.ApplicationEvent; +import org.springframework.core.ResolvableType; +import org.springframework.core.ResolvableTypeProvider; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.core.Authentication; @@ -32,7 +34,7 @@ * @since 5.7 */ @SuppressWarnings("serial") -public class AuthorizationGrantedEvent extends AuthorizationEvent { +public class AuthorizationGrantedEvent extends AuthorizationEvent implements ResolvableTypeProvider { @Serial private static final long serialVersionUID = -8690818228055810339L; @@ -65,4 +67,14 @@ public T getObject() { return (T) getSource(); } + /** + * Get {@link ResolvableType} of this class. + * @return {@link ResolvableType} + * @since 6.5 + */ + @Override + public ResolvableType getResolvableType() { + return ResolvableType.forClassWithGenerics(getClass(), ResolvableType.forInstance(getObject())); + } + } From 9179db999b2c35cbd859607b4378c569ca159673 Mon Sep 17 00:00:00 2001 From: Josh Cummings <3627351+jzheaux@users.noreply.github.com> Date: Thu, 20 Mar 2025 12:47:04 -0600 Subject: [PATCH 2/2] Add Authorization Event Tests - These ensure that the parameterized version of authorization events can be listened to Issue gh-16700 --- ...ePostMethodSecurityConfigurationTests.java | 37 +++++++++++++++++++ .../AuthorizeHttpRequestsConfigurerTests.java | 35 ++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java index 17d8f8a3a92..0dffadc5db6 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostMethodSecurityConfigurationTests.java @@ -51,11 +51,13 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.AdviceMode; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Role; +import org.springframework.context.event.EventListener; import org.springframework.core.annotation.AnnotationConfigurationException; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.PermissionEvaluator; @@ -76,6 +78,8 @@ import org.springframework.security.authorization.AuthorizationEventPublisher; import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationResult; +import org.springframework.security.authorization.SpringAuthorizationEventPublisher; +import org.springframework.security.authorization.event.AuthorizationDeniedEvent; import org.springframework.security.authorization.method.AuthorizationAdvisor; import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory; import org.springframework.security.authorization.method.AuthorizationAdvisorProxyFactory.TargetVisitor; @@ -1103,6 +1107,17 @@ public void jsr250MethodWhenExcludeAuthorizationObservationsThenUnobserved() { verifyNoInteractions(handler); } + @Test + @WithMockUser + public void preAuthorizeWhenDenyAllThenPublishesParameterizedAuthorizationDeniedEvent() { + this.spring + .register(MethodSecurityServiceConfig.class, EventPublisherConfig.class, AuthorizationDeniedListener.class) + .autowire(); + assertThatExceptionOfType(AccessDeniedException.class) + .isThrownBy(() -> this.methodSecurityService.preAuthorize()); + assertThat(this.spring.getContext().getBean(AuthorizationDeniedListener.class).invocations).isEqualTo(1); + } + private static Consumer disallowBeanOverriding() { return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false); } @@ -1795,4 +1810,26 @@ SecurityObservationSettings observabilityDefaults() { } + @Configuration + static class EventPublisherConfig { + + @Bean + static AuthorizationEventPublisher eventPublisher(ApplicationEventPublisher publisher) { + return new SpringAuthorizationEventPublisher(publisher); + } + + } + + @Component + static class AuthorizationDeniedListener { + + int invocations; + + @EventListener + void onRequestDenied(AuthorizationDeniedEvent denied) { + this.invocations++; + } + + } + } diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java index 57d2a92ded5..4d9c39c4b16 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/AuthorizeHttpRequestsConfigurerTests.java @@ -32,8 +32,10 @@ import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.event.EventListener; import org.springframework.security.access.hierarchicalroles.RoleHierarchy; import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl; import org.springframework.security.authentication.RememberMeAuthenticationToken; @@ -43,6 +45,8 @@ import org.springframework.security.authorization.AuthorizationManager; import org.springframework.security.authorization.AuthorizationObservationContext; import org.springframework.security.authorization.AuthorizationResult; +import org.springframework.security.authorization.SpringAuthorizationEventPublisher; +import org.springframework.security.authorization.event.AuthorizationDeniedEvent; import org.springframework.security.config.ObjectPostProcessor; import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -66,6 +70,7 @@ import org.springframework.security.web.access.intercept.RequestMatcherDelegatingAuthorizationManager; import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; +import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.RequestPostProcessor; @@ -670,6 +675,14 @@ public void getWhenExcludeAuthorizationObservationsThenUnobserved() throws Excep verifyNoInteractions(handler); } + @Test + public void getWhenDeniedThenParameterizedAuthorizationDeniedEventIsPublished() throws Exception { + this.spring.register(DenyAllConfig.class, EventPublisherConfig.class, AuthorizationDeniedListener.class) + .autowire(); + this.mvc.perform(get("/").with(user("user"))); + assertThat(this.spring.getContext().getBean(AuthorizationDeniedListener.class).invocations).isEqualTo(1); + } + @Test public void requestMatchersWhenMultipleDispatcherServletsAndPathBeanThenAllows() throws Exception { this.spring.register(MvcRequestMatcherBuilderConfig.class, BasicController.class) @@ -1390,4 +1403,26 @@ PathPatternRequestMatcherBuilderFactoryBean pathPatternFactoryBean() { } + @Configuration + static class EventPublisherConfig { + + @Bean + static AuthorizationEventPublisher eventPublisher(ApplicationEventPublisher publisher) { + return new SpringAuthorizationEventPublisher(publisher); + } + + } + + @Component + static class AuthorizationDeniedListener { + + int invocations; + + @EventListener + void onRequestDenied(AuthorizationDeniedEvent denied) { + this.invocations++; + } + + } + }