diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java index f6bc746ae2..d2b5a74006 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/PrePostReactiveMethodSecurityConfigurationTests.java @@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.method.configuration; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -58,6 +59,7 @@ import org.springframework.security.authorization.method.PrePostTemplateDefaults import org.springframework.security.config.Customizer; import org.springframework.security.config.test.SpringTestContext; import org.springframework.security.config.test.SpringTestContextExtension; +import org.springframework.security.core.Authentication; import org.springframework.security.core.annotation.AnnotationTemplateExpressionDefaults; import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; import org.springframework.security.test.context.support.WithMockUser; @@ -71,6 +73,7 @@ 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.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -449,6 +452,20 @@ public class PrePostReactiveMethodSecurityConfigurationTests { "postFilterAuthorizationMethodInterceptor", "authorizeReturnObjectMethodInterceptor"); } + // gh-15651 + @Test + @WithMockUser(roles = "ADMIN") + public void adviseWhenPrePostEnabledThenEachInterceptorRunsExactlyOnce() { + this.spring + .register(MethodSecurityServiceEnabledConfig.class, CustomMethodSecurityExpressionHandlerConfig.class) + .autowire(); + MethodSecurityExpressionHandler expressionHandler = this.spring.getContext() + .getBean(MethodSecurityExpressionHandler.class); + ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); + service.manyAnnotations(Mono.just(new ArrayList<>(Arrays.asList("harold", "jonathan", "tim", "bo")))).block(); + verify(expressionHandler, times(4)).createEvaluationContext(any(Authentication.class), any()); + } + @Configuration @EnableReactiveMethodSecurity static class MethodSecurityServiceEnabledConfig { @@ -460,6 +477,20 @@ public class PrePostReactiveMethodSecurityConfigurationTests { } + @Configuration + @EnableReactiveMethodSecurity + static class CustomMethodSecurityExpressionHandlerConfig { + + private final MethodSecurityExpressionHandler expressionHandler = spy( + new DefaultMethodSecurityExpressionHandler()); + + @Bean + MethodSecurityExpressionHandler methodSecurityExpressionHandler() { + return this.expressionHandler; + } + + } + @Configuration static class PermissionEvaluatorConfig { diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java index e2c3bef113..a3fee08102 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityService.java @@ -21,6 +21,7 @@ import java.lang.annotation.Inherited; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.util.List; import org.aopalliance.intercept.MethodInvocation; import reactor.core.publisher.Mono; @@ -31,7 +32,9 @@ import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostAuthorize; +import org.springframework.security.access.prepost.PostFilter; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.access.prepost.PreFilter; import org.springframework.security.authorization.AuthorizationResult; import org.springframework.security.authorization.method.HandleAuthorizationDenied; import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; @@ -104,6 +107,12 @@ public interface ReactiveMethodSecurityService { @PreAuthorize("hasPermission(#kgName, 'read')") Mono preAuthorizeHasPermission(String kgName); + @PreAuthorize("hasRole('ADMIN')") + @PostAuthorize("hasRole('ADMIN')") + @PreFilter("true") + @PostFilter("true") + Mono> manyAnnotations(Mono> array); + class StarMaskingHandler implements MethodAuthorizationDeniedHandler { @Override diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java index 3787556a87..7b8a893d17 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecurityServiceImpl.java @@ -16,6 +16,8 @@ package org.springframework.security.config.annotation.method.configuration; +import java.util.List; + import reactor.core.publisher.Mono; import org.springframework.security.authorization.AuthorizationDecision; @@ -93,4 +95,9 @@ public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurity return Mono.just("ok"); } + @Override + public Mono> manyAnnotations(Mono> array) { + return array; + } + }