diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.java index 24a500a36c6..04bb9003e8c 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurity.java @@ -75,6 +75,6 @@ * used. * @since 5.8 */ - boolean authorizationManager() default false; + boolean useAuthorizationManager() default false; } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java index 5ebb4f0944a..b2ed2ee22d4 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveAuthorizationManagerMethodSecurityConfiguration.java @@ -45,17 +45,15 @@ final class ReactiveAuthorizationManagerMethodSecurityConfiguration { @Role(BeanDefinition.ROLE_INFRASTRUCTURE) PreFilterAuthorizationReactiveMethodInterceptor preFilterInterceptor( MethodSecurityExpressionHandler expressionHandler) { - PreFilterAuthorizationReactiveMethodInterceptor preFilter = new PreFilterAuthorizationReactiveMethodInterceptor(); - preFilter.setExpressionHandler(expressionHandler); - return preFilter; + return new PreFilterAuthorizationReactiveMethodInterceptor(expressionHandler); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor( MethodSecurityExpressionHandler expressionHandler) { - PreAuthorizeReactiveAuthorizationManager authorizationManager = new PreAuthorizeReactiveAuthorizationManager(); - authorizationManager.setExpressionHandler(expressionHandler); + PreAuthorizeReactiveAuthorizationManager authorizationManager = new PreAuthorizeReactiveAuthorizationManager( + expressionHandler); return AuthorizationManagerBeforeReactiveMethodInterceptor.preAuthorize(authorizationManager); } @@ -63,17 +61,15 @@ AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorizeInterceptor( @Role(BeanDefinition.ROLE_INFRASTRUCTURE) PostFilterAuthorizationReactiveMethodInterceptor postFilterInterceptor( MethodSecurityExpressionHandler expressionHandler) { - PostFilterAuthorizationReactiveMethodInterceptor postFilter = new PostFilterAuthorizationReactiveMethodInterceptor(); - postFilter.setExpressionHandler(expressionHandler); - return postFilter; + return new PostFilterAuthorizationReactiveMethodInterceptor(expressionHandler); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) AuthorizationManagerAfterReactiveMethodInterceptor postAuthorizeInterceptor( MethodSecurityExpressionHandler expressionHandler) { - PostAuthorizeReactiveAuthorizationManager authorizationManager = new PostAuthorizeReactiveAuthorizationManager(); - authorizationManager.setExpressionHandler(expressionHandler); + PostAuthorizeReactiveAuthorizationManager authorizationManager = new PostAuthorizeReactiveAuthorizationManager( + expressionHandler); return AuthorizationManagerAfterReactiveMethodInterceptor.postAuthorize(authorizationManager); } diff --git a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java index fd0d304c359..0b679cb1f83 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java +++ b/config/src/main/java/org/springframework/security/config/annotation/method/configuration/ReactiveMethodSecuritySelector.java @@ -44,7 +44,7 @@ public String[] selectImports(AnnotationMetadata importMetadata) { EnableReactiveMethodSecurity annotation = importMetadata.getAnnotations() .get(EnableReactiveMethodSecurity.class).synthesize(); List imports = new ArrayList<>(Arrays.asList(this.autoProxy.selectImports(importMetadata))); - if (annotation.authorizationManager()) { + if (annotation.useAuthorizationManager()) { imports.add(ReactiveAuthorizationManagerMethodSecurityConfiguration.class.getName()); } else { diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/DelegatingReactiveMessageService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/DelegatingReactiveMessageService.java index dd1d5765e6c..dc700c4b315 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/DelegatingReactiveMessageService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/DelegatingReactiveMessageService.java @@ -112,6 +112,11 @@ public Flux fluxManyAnnotations(Flux flux) { return flux; } + @PostFilter("filterObject.length > 5") + public Flux fluxPostFilter(Flux flux) { + return flux; + } + @Override public Publisher publisherFindById(long id) { return this.delegate.publisherFindById(id); diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableAuthorizationManagerReactiveMethodSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableAuthorizationManagerReactiveMethodSecurityTests.java index 3f21e1bdfb2..4db37b61f68 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableAuthorizationManagerReactiveMethodSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableAuthorizationManagerReactiveMethodSecurityTests.java @@ -42,7 +42,7 @@ /** * Tests for {@link EnableReactiveMethodSecurity} with the - * {@link EnableReactiveMethodSecurity#authorizationManager()} flag set to true. + * {@link EnableReactiveMethodSecurity#useAuthorizationManager()} flag set to true. * * @author Evgeniy Cheban */ @@ -79,8 +79,7 @@ public void notPublisherPreAuthorizeFindByIdThenThrowsIllegalStateException() { .withMessage("The returnType class java.lang.String on public abstract java.lang.String " + "org.springframework.security.config.annotation.method.configuration.ReactiveMessageService" + ".notPublisherPreAuthorizeFindById(long) must return an instance of org.reactivestreams" - + ".Publisher (i.e. Mono / Flux) or the function must be a Kotlin coroutine " - + "function in order to support Reactor Context"); + + ".Publisher (for example, a Mono or Flux) in order to support Reactor Context"); } @Test @@ -340,6 +339,13 @@ public void fluxManyAnnotationsWhenNameNotAllowedThenFails() { StepVerifier.create(flux).expectNext("harold", "jonathan").expectError(AccessDeniedException.class).verify(); } + @Test + public void fluxPostFilterWhenFilteringThenWorks() { + Flux flux = this.messageService.fluxPostFilter(Flux.just("harold", "jonathan", "michael", "pete", "bo")) + .contextWrite(this.withAdmin); + StepVerifier.create(flux).expectNext("harold", "jonathan", "michael").verifyComplete(); + } + // Publisher tests @Test public void publisherWhenPermitAllThenAopDoesNotSubscribe() { @@ -458,7 +464,7 @@ static Publisher publisherJust(T... data) { return publisher(Flux.just(data)); } - @EnableReactiveMethodSecurity(authorizationManager = true) + @EnableReactiveMethodSecurity(useAuthorizationManager = true) static class Config { ReactiveMessageService delegate = mock(ReactiveMessageService.class); diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMessageService.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMessageService.java index 63ea868cd24..2adf54a395d 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMessageService.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/ReactiveMessageService.java @@ -48,6 +48,8 @@ public interface ReactiveMessageService { Flux fluxManyAnnotations(Flux flux); + Flux fluxPostFilter(Flux flux); + Publisher publisherFindById(long id); Publisher publisherPreAuthorizeHasRoleFindById(long id); diff --git a/core/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java index b3ca0d5549d..d26014ee6e2 100644 --- a/core/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/access/prepost/PrePostAdviceReactiveMethodInterceptor.java @@ -52,7 +52,12 @@ * @author Rob Winch * @author Eleftheria Stein * @since 5.0 + * @deprecated Use + * {@link org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor} + * or + * {@link org.springframework.security.authorization.method.AuthorizationManagerAfterReactiveMethodInterceptor} */ +@Deprecated public class PrePostAdviceReactiveMethodInterceptor implements MethodInterceptor { private Authentication anonymous = new AnonymousAuthenticationToken("key", "anonymous", diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAfterReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAfterReactiveMethodInterceptor.java deleted file mode 100644 index ae43393e52b..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationAfterReactiveMethodInterceptor.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import java.lang.reflect.Method; - -import org.aopalliance.aop.Advice; -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -import org.springframework.aop.Pointcut; -import org.springframework.aop.PointcutAdvisor; -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.core.Ordered; - -/** - * A {@link MethodInterceptor} that wraps a {@link Mono} or a {@link Flux} using - * deffer call. - * - * @author Evgeniy Cheban - * @since 5.8 - */ -final class AuthorizationAfterReactiveMethodInterceptor - implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - - private final Pointcut pointcut = AuthorizationMethodPointcuts.forAllAnnotations(); - - private final int order = AuthorizationInterceptorsOrder.LAST.getOrder(); - - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - Method method = mi.getMethod(); - Class returnType = method.getReturnType(); - if (Mono.class.isAssignableFrom(returnType)) { - return Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); - } - return Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); - } - - @Override - public Pointcut getPointcut() { - return this.pointcut; - } - - @Override - public Advice getAdvice() { - return this; - } - - @Override - public boolean isPerInstance() { - return true; - } - - @Override - public int getOrder() { - return this.order; - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationBeanFactoryPostProcessor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationBeanFactoryPostProcessor.java deleted file mode 100644 index b622562c63a..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationBeanFactoryPostProcessor.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; -import org.springframework.beans.factory.support.RootBeanDefinition; - -/** - * Adds {@link AuthorizationBeforeReactiveMethodInterceptor} and - * {@link AuthorizationAfterReactiveMethodInterceptor} bean definitions to the - * {@link BeanDefinitionRegistry} if they have not already been added. - * - * @author Evgeniy Cheban - * @since 5.8 - */ -final class AuthorizationBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor { - - private static final String BEFORE_INTERCEPTOR_BEAN_NAME = "org.springframework.security.authorization.method.authorizationBeforeReactiveMethodInterceptor"; - - private static final String AFTER_INTERCEPTOR_BEAN_NAME = "org.springframework.security.authorization.method.authorizationAfterReactiveMethodInterceptor"; - - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { - if (!registry.containsBeanDefinition(BEFORE_INTERCEPTOR_BEAN_NAME)) { - RootBeanDefinition beforeInterceptor = new RootBeanDefinition( - AuthorizationBeforeReactiveMethodInterceptor.class); - beforeInterceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(BEFORE_INTERCEPTOR_BEAN_NAME, beforeInterceptor); - } - if (!registry.containsBeanDefinition(AFTER_INTERCEPTOR_BEAN_NAME)) { - RootBeanDefinition afterInterceptor = new RootBeanDefinition( - AuthorizationAfterReactiveMethodInterceptor.class); - afterInterceptor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - registry.registerBeanDefinition(AFTER_INTERCEPTOR_BEAN_NAME, afterInterceptor); - } - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationBeforeReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationBeforeReactiveMethodInterceptor.java deleted file mode 100644 index 2462fb782fd..00000000000 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationBeforeReactiveMethodInterceptor.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2002-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.security.authorization.method; - -import java.lang.reflect.Method; - -import org.aopalliance.aop.Advice; -import org.aopalliance.intercept.MethodInterceptor; -import org.aopalliance.intercept.MethodInvocation; -import org.reactivestreams.Publisher; - -import org.springframework.aop.Pointcut; -import org.springframework.aop.PointcutAdvisor; -import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.core.Ordered; -import org.springframework.core.ReactiveAdapter; -import org.springframework.core.ReactiveAdapterRegistry; -import org.springframework.util.Assert; - -/** - * A {@link MethodInterceptor} which validates and transforms the return type for methods - * that return a {@link Publisher}. - * - * @author Evgeniy Cheban - * @since 5.8 - */ -final class AuthorizationBeforeReactiveMethodInterceptor - implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - - private final Pointcut pointcut = AuthorizationMethodPointcuts.forAllAnnotations(); - - private final int order = AuthorizationInterceptorsOrder.FIRST.getOrder(); - - @Override - public Object invoke(MethodInvocation mi) throws Throwable { - Method method = mi.getMethod(); - Class returnType = method.getReturnType(); - Assert.state(Publisher.class.isAssignableFrom(returnType), - () -> "The returnType " + returnType + " on " + method - + " must return an instance of org.reactivestreams.Publisher " - + "(i.e. Mono / Flux) or the function must be a Kotlin coroutine " - + "function in order to support Reactor Context"); - Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); - ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(returnType); - return (adapter != null) ? adapter.fromPublisher(publisher) : publisher; - } - - @Override - public Pointcut getPointcut() { - return this.pointcut; - } - - @Override - public Advice getAdvice() { - return this; - } - - @Override - public boolean isPerInstance() { - return true; - } - - @Override - public int getOrder() { - return this.order; - } - -} diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java index 91010d85c46..c33c36e8f28 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptor.java @@ -16,6 +16,9 @@ package org.springframework.security.authorization.method; +import java.lang.reflect.Method; +import java.util.function.Function; + import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -26,10 +29,9 @@ import org.springframework.aop.Pointcut; import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.core.Ordered; +import org.springframework.core.ReactiveAdapter; +import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; @@ -43,16 +45,14 @@ * @author Evgeniy Cheban * @since 5.8 */ -public final class AuthorizationManagerAfterReactiveMethodInterceptor implements Ordered, MethodInterceptor, - PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor { - - private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor(); +public final class AuthorizationManagerAfterReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private final Pointcut pointcut; private final ReactiveAuthorizationManager authorizationManager; - private int order = AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder(); + private int order = AuthorizationInterceptorsOrder.LAST.getOrder(); /** * Creates an instance for the {@link PostAuthorize} annotation. @@ -69,8 +69,10 @@ public static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorize() */ public static AuthorizationManagerAfterReactiveMethodInterceptor postAuthorize( ReactiveAuthorizationManager authorizationManager) { - return new AuthorizationManagerAfterReactiveMethodInterceptor( + AuthorizationManagerAfterReactiveMethodInterceptor interceptor = new AuthorizationManagerAfterReactiveMethodInterceptor( AuthorizationMethodPointcuts.forAnnotations(PostAuthorize.class), authorizationManager); + interceptor.setOrder(AuthorizationInterceptorsOrder.POST_AUTHORIZE.getOrder()); + return interceptor; } /** @@ -95,13 +97,28 @@ public AuthorizationManagerAfterReactiveMethodInterceptor(Pointcut pointcut, */ @Override public Object invoke(MethodInvocation mi) throws Throwable { - Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); + Method method = mi.getMethod(); + Class type = method.getReturnType(); + Assert.state(Publisher.class.isAssignableFrom(type), + () -> String.format("The returnType %s on %s must return an instance of org.reactivestreams.Publisher " + + "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); - if (publisher instanceof Mono) { - Mono mono = (Mono) publisher; - return mono.flatMap((result) -> postAuthorize(authentication, mi, result)); + Function> postAuthorize = (result) -> postAuthorize(authentication, mi, result); + ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); + Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); + if (isMultiValue(type, adapter)) { + Flux flux = Flux.from(publisher).flatMap(postAuthorize); + return (adapter != null) ? adapter.fromPublisher(flux) : flux; + } + Mono mono = Mono.from(publisher).flatMap(postAuthorize); + return (adapter != null) ? adapter.fromPublisher(mono) : mono; + } + + private boolean isMultiValue(Class returnType, ReactiveAdapter adapter) { + if (Flux.class.isAssignableFrom(returnType)) { + return true; } - return Flux.from(publisher).flatMap((result) -> postAuthorize(authentication, mi, result)); + return adapter == null || adapter.isMultiValue(); } private Mono postAuthorize(Mono authentication, MethodInvocation mi, Object result) { @@ -133,14 +150,4 @@ public void setOrder(int order) { this.order = order; } - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { - this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry); - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java index c0e213cd09e..8aedb5ad3be 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptor.java @@ -16,19 +16,21 @@ package org.springframework.security.authorization.method; +import java.lang.reflect.Method; + import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.reactivestreams.Publisher; +import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.aop.Pointcut; import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.core.Ordered; +import org.springframework.core.ReactiveAdapter; +import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; @@ -40,18 +42,17 @@ * {@link ReactiveAuthorizationManager}. * * @author Evgeniy Cheban + * @author Josh Cummings * @since 5.8 */ -public final class AuthorizationManagerBeforeReactiveMethodInterceptor implements Ordered, MethodInterceptor, - PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor { - - private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor(); +public final class AuthorizationManagerBeforeReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { private final Pointcut pointcut; private final ReactiveAuthorizationManager authorizationManager; - private int order = AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder(); + private int order = AuthorizationInterceptorsOrder.FIRST.getOrder(); /** * Creates an instance for the {@link PreAuthorize} annotation. @@ -68,8 +69,10 @@ public static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorize() */ public static AuthorizationManagerBeforeReactiveMethodInterceptor preAuthorize( ReactiveAuthorizationManager authorizationManager) { - return new AuthorizationManagerBeforeReactiveMethodInterceptor( + AuthorizationManagerBeforeReactiveMethodInterceptor interceptor = new AuthorizationManagerBeforeReactiveMethodInterceptor( AuthorizationMethodPointcuts.forAnnotations(PreAuthorize.class), authorizationManager); + interceptor.setOrder(AuthorizationInterceptorsOrder.PRE_AUTHORIZE.getOrder()); + return interceptor; } /** @@ -94,13 +97,29 @@ public AuthorizationManagerBeforeReactiveMethodInterceptor(Pointcut pointcut, */ @Override public Object invoke(MethodInvocation mi) throws Throwable { - Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); + Method method = mi.getMethod(); + Class type = method.getReturnType(); + Assert.state(Publisher.class.isAssignableFrom(type), + () -> String.format("The returnType %s on %s must return an instance of org.reactivestreams.Publisher " + + "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); Mono authentication = ReactiveAuthenticationUtils.getAuthentication(); + ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); Mono preAuthorize = this.authorizationManager.verify(authentication, mi); - if (publisher instanceof Mono) { - return preAuthorize.then((Mono) publisher); + if (isMultiValue(type, adapter)) { + Publisher publisher = Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); + Flux result = preAuthorize.thenMany(publisher); + return (adapter != null) ? adapter.fromPublisher(result) : result; + } + Mono publisher = Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); + Mono result = preAuthorize.then(publisher); + return (adapter != null) ? adapter.fromPublisher(result) : result; + } + + private boolean isMultiValue(Class returnType, ReactiveAdapter adapter) { + if (Flux.class.isAssignableFrom(returnType)) { + return true; } - return preAuthorize.thenMany(publisher); + return adapter == null || adapter.isMultiValue(); } @Override @@ -127,14 +146,4 @@ public void setOrder(int order) { this.order = order; } - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { - this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry); - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java index dab71e30c9e..0835a45acb3 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeAuthorizationManager.java @@ -39,14 +39,14 @@ */ public final class PostAuthorizeAuthorizationManager implements AuthorizationManager { - private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); + private PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); /** * Use this the {@link MethodSecurityExpressionHandler}. * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java index 7ea4e3c9c44..dcae9b0d72a 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeExpressionAttributeRegistry.java @@ -35,17 +35,21 @@ */ final class PostAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + private final MethodSecurityExpressionHandler expressionHandler; - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; + PostAuthorizeExpressionAttributeRegistry() { + this(new DefaultMethodSecurityExpressionHandler()); } - void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { + PostAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); this.expressionHandler = expressionHandler; } + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; + } + @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java index ad34c3d009d..214fa9654eb 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManager.java @@ -19,11 +19,13 @@ import org.aopalliance.intercept.MethodInvocation; import reactor.core.publisher.Mono; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PostAuthorize; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; /** * A {@link ReactiveAuthorizationManager} which can determine if an {@link Authentication} @@ -36,14 +38,15 @@ public final class PostAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager { - private final PostAuthorizeExpressionAttributeRegistry registry = new PostAuthorizeExpressionAttributeRegistry(); + private final PostAuthorizeExpressionAttributeRegistry registry; - /** - * Sets the {@link MethodSecurityExpressionHandler}. - * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use - */ - public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + public PostAuthorizeReactiveAuthorizationManager() { + this(new DefaultMethodSecurityExpressionHandler()); + } + + public PostAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.registry = new PostAuthorizeExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java index 8843b4345fa..4e58611b6d2 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationMethodInterceptor.java @@ -49,7 +49,7 @@ public final class PostFilterAuthorizationMethodInterceptor private Supplier authentication = getAuthentication( SecurityContextHolder.getContextHolderStrategy()); - private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry(); + private PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry(); private int order = AuthorizationInterceptorsOrder.POST_FILTER.getOrder(); @@ -68,7 +68,7 @@ public PostFilterAuthorizationMethodInterceptor() { * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java index 601cb10a2e7..759a63dd4ac 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptor.java @@ -16,6 +16,8 @@ package org.springframework.security.authorization.method; +import java.lang.reflect.Method; + import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -26,14 +28,15 @@ import org.springframework.aop.Pointcut; import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.framework.AopInfrastructureBean; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.core.Ordered; +import org.springframework.core.ReactiveAdapter; +import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.expression.EvaluationContext; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.access.prepost.PostFilter; +import org.springframework.util.Assert; /** * A {@link MethodInterceptor} which filters the returned object from the @@ -43,14 +46,12 @@ * @author Evgeniy Cheban * @since 5.8 */ -public final class PostFilterAuthorizationReactiveMethodInterceptor implements Ordered, MethodInterceptor, - PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor { - - private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor(); +public final class PostFilterAuthorizationReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - private final PostFilterExpressionAttributeRegistry registry = new PostFilterExpressionAttributeRegistry(); + private final PostFilterExpressionAttributeRegistry registry; - private final Pointcut pointcut; + private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class); private int order = AuthorizationInterceptorsOrder.POST_FILTER.getOrder(); @@ -58,15 +59,15 @@ public final class PostFilterAuthorizationReactiveMethodInterceptor implements O * Creates an instance. */ public PostFilterAuthorizationReactiveMethodInterceptor() { - this.pointcut = AuthorizationMethodPointcuts.forAnnotations(PostFilter.class); + this(new DefaultMethodSecurityExpressionHandler()); } /** - * Sets the {@link MethodSecurityExpressionHandler}. - * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use + * Creates an instance. */ - public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + public PostFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.registry = new PostFilterExpressionAttributeRegistry(expressionHandler); } /** @@ -77,25 +78,41 @@ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandl */ @Override public Object invoke(MethodInvocation mi) throws Throwable { - Publisher publisher = ReactiveMethodInvocationUtils.proceed(mi); ExpressionAttribute attribute = this.registry.getAttribute(mi); if (attribute == ExpressionAttribute.NULL_ATTRIBUTE) { - return publisher; + return ReactiveMethodInvocationUtils.proceed(mi); } Mono toInvoke = ReactiveAuthenticationUtils.getAuthentication() .map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi)); - if (publisher instanceof Mono) { - return toInvoke.flatMap((ctx) -> filterMono((Mono) publisher, ctx, attribute)); + Method method = mi.getMethod(); + Class type = method.getReturnType(); + Assert.state(Publisher.class.isAssignableFrom(type), + () -> String.format("The parameter type %s on %s must be an instance of org.reactivestreams.Publisher " + + "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); + ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); + if (isMultiValue(type, adapter)) { + Publisher publisher = Flux.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); + Flux flux = toInvoke.flatMapMany((ctx) -> filterMultiValue(publisher, ctx, attribute)); + return (adapter != null) ? adapter.fromPublisher(flux) : flux; + } + Publisher publisher = Mono.defer(() -> ReactiveMethodInvocationUtils.proceed(mi)); + Mono mono = toInvoke.flatMap((ctx) -> filterSingleValue(publisher, ctx, attribute)); + return (adapter != null) ? adapter.fromPublisher(mono) : mono; + } + + private boolean isMultiValue(Class returnType, ReactiveAdapter adapter) { + if (Flux.class.isAssignableFrom(returnType)) { + return true; } - return toInvoke.flatMapMany((ctx) -> filterPublisher(publisher, ctx, attribute)); + return adapter == null || adapter.isMultiValue(); } - private Mono filterMono(Mono mono, EvaluationContext ctx, ExpressionAttribute attribute) { - return mono.doOnNext((result) -> setFilterObject(ctx, result)) + private Mono filterSingleValue(Publisher publisher, EvaluationContext ctx, ExpressionAttribute attribute) { + return Mono.from(publisher).doOnNext((result) -> setFilterObject(ctx, result)) .flatMap((result) -> postFilter(ctx, result, attribute)); } - private Flux filterPublisher(Publisher publisher, EvaluationContext ctx, ExpressionAttribute attribute) { + private Flux filterMultiValue(Publisher publisher, EvaluationContext ctx, ExpressionAttribute attribute) { return Flux.from(publisher).doOnNext((result) -> setFilterObject(ctx, result)) .flatMap((result) -> postFilter(ctx, result, attribute)); } @@ -133,14 +150,4 @@ public void setOrder(int order) { this.order = order; } - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { - this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry); - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory); - } - } diff --git a/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java index 21dff773646..44bc2802ab9 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PostFilterExpressionAttributeRegistry.java @@ -34,17 +34,21 @@ */ final class PostFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + private final MethodSecurityExpressionHandler expressionHandler; - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; + PostFilterExpressionAttributeRegistry() { + this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); } - void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { + PostFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); this.expressionHandler = expressionHandler; } + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; + } + @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java index 987999dd5c8..b58a3a7b7ef 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeAuthorizationManager.java @@ -39,14 +39,14 @@ */ public final class PreAuthorizeAuthorizationManager implements AuthorizationManager { - private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); + private PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); /** * Sets the {@link MethodSecurityExpressionHandler}. * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java index 62bf863cd7d..7cced570d1a 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeExpressionAttributeRegistry.java @@ -35,7 +35,16 @@ */ final class PreAuthorizeExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + private final MethodSecurityExpressionHandler expressionHandler; + + PreAuthorizeExpressionAttributeRegistry() { + this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); + } + + PreAuthorizeExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.expressionHandler = expressionHandler; + } /** * Returns the {@link MethodSecurityExpressionHandler}. @@ -45,15 +54,6 @@ MethodSecurityExpressionHandler getExpressionHandler() { return this.expressionHandler; } - /** - * Sets the {@link MethodSecurityExpressionHandler}. - * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use - */ - void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - Assert.notNull(expressionHandler, "expressionHandler cannot be null"); - this.expressionHandler = expressionHandler; - } - @NonNull @Override ExpressionAttribute resolveAttribute(Method method, Class targetClass) { diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java index 7ff923300a3..cffdf832354 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManager.java @@ -19,11 +19,13 @@ import org.aopalliance.intercept.MethodInvocation; import reactor.core.publisher.Mono; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.authorization.AuthorizationDecision; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.Authentication; +import org.springframework.util.Assert; /** * A {@link ReactiveAuthorizationManager} which can determine if an {@link Authentication} @@ -35,14 +37,15 @@ */ public final class PreAuthorizeReactiveAuthorizationManager implements ReactiveAuthorizationManager { - private final PreAuthorizeExpressionAttributeRegistry registry = new PreAuthorizeExpressionAttributeRegistry(); + private final PreAuthorizeExpressionAttributeRegistry registry; - /** - * Sets the {@link MethodSecurityExpressionHandler}. - * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use - */ - public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + public PreAuthorizeReactiveAuthorizationManager() { + this(new DefaultMethodSecurityExpressionHandler()); + } + + public PreAuthorizeReactiveAuthorizationManager(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.registry = new PreAuthorizeExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java index 06d2472cd22..a0a9e30171b 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationMethodInterceptor.java @@ -50,7 +50,7 @@ public final class PreFilterAuthorizationMethodInterceptor private Supplier authentication = getAuthentication( SecurityContextHolder.getContextHolderStrategy()); - private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry(); + private PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry(); private int order = AuthorizationInterceptorsOrder.PRE_FILTER.getOrder(); @@ -69,7 +69,7 @@ public PreFilterAuthorizationMethodInterceptor() { * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use */ public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler); } /** diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java index 3746e71fd60..a7af3b037c7 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptor.java @@ -29,15 +29,13 @@ import org.springframework.aop.PointcutAdvisor; import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.support.AopUtils; -import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; -import org.springframework.beans.factory.support.BeanDefinitionRegistry; -import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor; import org.springframework.core.Ordered; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ReactiveAdapter; import org.springframework.core.ReactiveAdapterRegistry; import org.springframework.expression.EvaluationContext; import org.springframework.expression.Expression; +import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations; import org.springframework.security.access.prepost.PreFilter; @@ -52,12 +50,10 @@ * @author Evgeniy Cheban * @since 5.8 */ -public final class PreFilterAuthorizationReactiveMethodInterceptor implements Ordered, MethodInterceptor, - PointcutAdvisor, AopInfrastructureBean, BeanDefinitionRegistryPostProcessor { +public final class PreFilterAuthorizationReactiveMethodInterceptor + implements Ordered, MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { - private final AuthorizationBeanFactoryPostProcessor beanFactoryPostProcessor = new AuthorizationBeanFactoryPostProcessor(); - - private final PreFilterExpressionAttributeRegistry registry = new PreFilterExpressionAttributeRegistry(); + private final PreFilterExpressionAttributeRegistry registry; private final Pointcut pointcut = AuthorizationMethodPointcuts.forAnnotations(PreFilter.class); @@ -65,12 +61,16 @@ public final class PreFilterAuthorizationReactiveMethodInterceptor implements Or private int order = AuthorizationInterceptorsOrder.PRE_FILTER.getOrder(); + public PreFilterAuthorizationReactiveMethodInterceptor() { + this(new DefaultMethodSecurityExpressionHandler()); + } + /** - * Sets the {@link MethodSecurityExpressionHandler}. - * @param expressionHandler the {@link MethodSecurityExpressionHandler} to use + * Creates an instance. */ - public void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { - this.registry.setExpressionHandler(expressionHandler); + public PreFilterAuthorizationReactiveMethodInterceptor(MethodSecurityExpressionHandler expressionHandler) { + Assert.notNull(expressionHandler, "expressionHandler cannot be null"); + this.registry = new PreFilterExpressionAttributeRegistry(expressionHandler); } /** @@ -92,23 +92,28 @@ public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDisc public Object invoke(MethodInvocation mi) throws Throwable { PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute attribute = this.registry.getAttribute(mi); if (attribute == PreFilterExpressionAttributeRegistry.PreFilterExpressionAttribute.NULL_ATTRIBUTE) { - return ReactiveMethodInvocationUtils.>proceed(mi); + return ReactiveMethodInvocationUtils.proceed(mi); } FilterTarget filterTarget = findFilterTarget(attribute.getFilterTarget(), mi); Mono toInvoke = ReactiveAuthenticationUtils.getAuthentication() .map((auth) -> this.registry.getExpressionHandler().createEvaluationContext(auth, mi)); - if (filterTarget.value instanceof Mono) { - mi.getArguments()[filterTarget.index] = toInvoke - .flatMap((ctx) -> filterMono((Mono) filterTarget.value, attribute.getExpression(), ctx)); + Method method = mi.getMethod(); + Class type = filterTarget.value.getClass(); + Assert.state(Publisher.class.isAssignableFrom(type), + () -> String.format("The parameter type %s on %s must be an instance of org.reactivestreams.Publisher " + + "(for example, a Mono or Flux) in order to support Reactor Context", type, method)); + ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(type); + if (isMultiValue(type, adapter)) { + Flux result = toInvoke + .flatMapMany((ctx) -> filterMultiValue(filterTarget.value, attribute.getExpression(), ctx)); + mi.getArguments()[filterTarget.index] = (adapter != null) ? adapter.fromPublisher(result) : result; } else { - Flux result = toInvoke - .flatMapMany((ctx) -> filterPublisher(filterTarget.value, attribute.getExpression(), ctx)); - ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance() - .getAdapter(filterTarget.value.getClass()); + Mono result = toInvoke + .flatMap((ctx) -> filterSingleValue(filterTarget.value, attribute.getExpression(), ctx)); mi.getArguments()[filterTarget.index] = (adapter != null) ? adapter.fromPublisher(result) : result; } - return ReactiveMethodInvocationUtils.>proceed(mi); + return ReactiveMethodInvocationUtils.proceed(mi); } private FilterTarget findFilterTarget(String name, MethodInvocation mi) { @@ -143,16 +148,23 @@ private FilterTarget findFilterTarget(String name, MethodInvocation mi) { return new FilterTarget((Publisher) value, index); } - private Mono filterMono(Mono filterTarget, Expression filterExpression, EvaluationContext ctx) { + private boolean isMultiValue(Class returnType, ReactiveAdapter adapter) { + if (Flux.class.isAssignableFrom(returnType)) { + return true; + } + return adapter == null || adapter.isMultiValue(); + } + + private Mono filterSingleValue(Publisher filterTarget, Expression filterExpression, EvaluationContext ctx) { MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject() .getValue(); - return filterTarget.filterWhen((filterObject) -> { + return Mono.from(filterTarget).filterWhen((filterObject) -> { rootObject.setFilterObject(filterObject); return ReactiveExpressionUtils.evaluateAsBoolean(filterExpression, ctx); }); } - private Flux filterPublisher(Publisher filterTarget, Expression filterExpression, EvaluationContext ctx) { + private Flux filterMultiValue(Publisher filterTarget, Expression filterExpression, EvaluationContext ctx) { MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx.getRootObject() .getValue(); return Flux.from(filterTarget).filterWhen((filterObject) -> { @@ -185,16 +197,6 @@ public void setOrder(int order) { this.order = order; } - @Override - public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { - this.beanFactoryPostProcessor.postProcessBeanDefinitionRegistry(registry); - } - - @Override - public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) { - this.beanFactoryPostProcessor.postProcessBeanFactory(beanFactory); - } - private static final class FilterTarget { private final Publisher value; diff --git a/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java b/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java index 7d5e0befb47..b8b68233808 100644 --- a/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java +++ b/core/src/main/java/org/springframework/security/authorization/method/PreFilterExpressionAttributeRegistry.java @@ -35,17 +35,21 @@ final class PreFilterExpressionAttributeRegistry extends AbstractExpressionAttributeRegistry { - private MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); + private final MethodSecurityExpressionHandler expressionHandler; - MethodSecurityExpressionHandler getExpressionHandler() { - return this.expressionHandler; + PreFilterExpressionAttributeRegistry() { + this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); } - void setExpressionHandler(MethodSecurityExpressionHandler expressionHandler) { + PreFilterExpressionAttributeRegistry(MethodSecurityExpressionHandler expressionHandler) { Assert.notNull(expressionHandler, "expressionHandler cannot be null"); this.expressionHandler = expressionHandler; } + MethodSecurityExpressionHandler getExpressionHandler() { + return this.expressionHandler; + } + @NonNull @Override PreFilterExpressionAttribute resolveAttribute(Method method, Class targetClass) { diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java index 5b03baa7552..199941872ed 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerAfterReactiveMethodInterceptorTests.java @@ -24,6 +24,7 @@ import org.springframework.aop.Pointcut; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.intercept.method.MockMethodInvocation; import org.springframework.security.authorization.ReactiveAuthorizationManager; import static org.assertj.core.api.Assertions.assertThat; @@ -32,6 +33,7 @@ import static org.mockito.ArgumentMatchers.any; 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; @@ -59,7 +61,8 @@ public void instantiateWhenAuthorizationManagerNullThenException() { @Test public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable { - MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocation mockMethodInvocation = spy( + new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); @@ -74,7 +77,8 @@ public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Th @Test public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable { - MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocation mockMethodInvocation = spy( + new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); @@ -89,7 +93,8 @@ public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Th @Test public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedException() throws Throwable { - MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocation mockMethodInvocation = spy( + new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); @@ -104,4 +109,16 @@ public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedExce verify(mockReactiveAuthorizationManager).verify(any(), any()); } + class Sample { + + Mono mono() { + return Mono.just("john"); + } + + Flux flux() { + return Flux.just("john", "bob"); + } + + } + } diff --git a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java index 2127953685d..f63b331138d 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/AuthorizationManagerBeforeReactiveMethodInterceptorTests.java @@ -24,6 +24,7 @@ import org.springframework.aop.Pointcut; import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.access.intercept.method.MockMethodInvocation; import org.springframework.security.authorization.ReactiveAuthorizationManager; import static org.assertj.core.api.Assertions.assertThat; @@ -33,6 +34,7 @@ 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.verify; /** @@ -60,7 +62,8 @@ public void instantiateWhenAuthorizationManagerNullThenException() { @Test public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable { - MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocation mockMethodInvocation = spy( + new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); @@ -75,7 +78,8 @@ public void invokeMonoWhenMockReactiveAuthorizationManagerThenVerify() throws Th @Test public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Throwable { - MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocation mockMethodInvocation = spy( + new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("flux"))); given(mockMethodInvocation.proceed()).willReturn(Flux.just("john", "bob")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); @@ -90,7 +94,8 @@ public void invokeFluxWhenMockReactiveAuthorizationManagerThenVerify() throws Th @Test public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedException() throws Throwable { - MethodInvocation mockMethodInvocation = mock(MethodInvocation.class); + MethodInvocation mockMethodInvocation = spy( + new MockMethodInvocation(new Sample(), Sample.class.getDeclaredMethod("mono"))); given(mockMethodInvocation.proceed()).willReturn(Mono.just("john")); ReactiveAuthorizationManager mockReactiveAuthorizationManager = mock( ReactiveAuthorizationManager.class); @@ -105,4 +110,16 @@ public void invokeWhenMockReactiveAuthorizationManagerDeniedThenAccessDeniedExce verify(mockReactiveAuthorizationManager).verify(any(), eq(mockMethodInvocation)); } + class Sample { + + Mono mono() { + return Mono.just("john"); + } + + Flux flux() { + return Flux.just("john", "bob"); + } + + } + } diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManagerTests.java index d7d8083ccc2..61cec711ecd 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PostAuthorizeReactiveAuthorizationManagerTests.java @@ -48,15 +48,14 @@ public class PostAuthorizeReactiveAuthorizationManagerTests { @Test public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() { MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager(); - manager.setExpressionHandler(expressionHandler); + PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager( + expressionHandler); assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); } @Test public void setExpressionHandlerWhenNullThenException() { - PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager(); - assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null)) + assertThatIllegalArgumentException().isThrownBy(() -> new PostAuthorizeReactiveAuthorizationManager(null)) .withMessage("expressionHandler cannot be null"); } diff --git a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptorTests.java index 000c080c706..fdea2e7e234 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PostFilterAuthorizationReactiveMethodInterceptorTests.java @@ -44,15 +44,15 @@ public class PostFilterAuthorizationReactiveMethodInterceptorTests { @Test public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() { MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor(); - interceptor.setExpressionHandler(expressionHandler); + PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor( + expressionHandler); assertThat(interceptor).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); } @Test public void setExpressionHandlerWhenNullThenException() { - PostFilterAuthorizationReactiveMethodInterceptor interceptor = new PostFilterAuthorizationReactiveMethodInterceptor(); - assertThatIllegalArgumentException().isThrownBy(() -> interceptor.setExpressionHandler(null)) + assertThatIllegalArgumentException() + .isThrownBy(() -> new PostFilterAuthorizationReactiveMethodInterceptor(null)) .withMessage("expressionHandler cannot be null"); } diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java index f4ba70921e2..b5273d42b7a 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PreAuthorizeReactiveAuthorizationManagerTests.java @@ -45,15 +45,14 @@ public class PreAuthorizeReactiveAuthorizationManagerTests { @Test public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() { MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager(); - manager.setExpressionHandler(expressionHandler); + PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager( + expressionHandler); assertThat(manager).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); } @Test public void setExpressionHandlerWhenNullThenException() { - PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager(); - assertThatIllegalArgumentException().isThrownBy(() -> manager.setExpressionHandler(null)) + assertThatIllegalArgumentException().isThrownBy(() -> new PreAuthorizeReactiveAuthorizationManager(null)) .withMessage("expressionHandler cannot be null"); } diff --git a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptorTests.java b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptorTests.java index 65430c4f61f..913018842b6 100644 --- a/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptorTests.java +++ b/core/src/test/java/org/springframework/security/authorization/method/PreFilterAuthorizationReactiveMethodInterceptorTests.java @@ -46,15 +46,14 @@ public class PreFilterAuthorizationReactiveMethodInterceptorTests { @Test public void setExpressionHandlerWhenNotNullThenSetsExpressionHandler() { MethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler(); - PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor(); - interceptor.setExpressionHandler(expressionHandler); + PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor( + expressionHandler); assertThat(interceptor).extracting("registry").extracting("expressionHandler").isEqualTo(expressionHandler); } @Test public void setExpressionHandlerWhenNullThenException() { - PreFilterAuthorizationReactiveMethodInterceptor interceptor = new PreFilterAuthorizationReactiveMethodInterceptor(); - assertThatIllegalArgumentException().isThrownBy(() -> interceptor.setExpressionHandler(null)) + assertThatIllegalArgumentException().isThrownBy(() -> new PreFilterAuthorizationReactiveMethodInterceptor(null)) .withMessage("expressionHandler cannot be null"); }