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 abeade38cc..652a6c9135 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 @@ -28,6 +28,12 @@ public class DelegatingReactiveMessageService implements ReactiveMessageService this.delegate = delegate; } + @Override + @PreAuthorize("denyAll") + public String notPublisherPreAuthorizeFindById(long id) { + return this.delegate.notPublisherPreAuthorizeFindById(id); + } + @Override public Mono monoFindById(long id) { return delegate.monoFindById(id); diff --git a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurityTests.java b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurityTests.java index c19f3a6089..1199f08588 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurityTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/method/configuration/EnableReactiveMethodSecurityTests.java @@ -34,6 +34,7 @@ import reactor.test.StepVerifier; import reactor.test.publisher.TestPublisher; import reactor.util.context.Context; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.Mockito.*; /** @@ -60,6 +61,14 @@ public class EnableReactiveMethodSecurityTests { this.delegate = config.delegate; } + @Test + public void notPublisherPreAuthorizeFindByIdThenThrowsIllegalStateException() { + assertThatThrownBy(() -> this.messageService.notPublisherPreAuthorizeFindById(1L)) + .isInstanceOf(IllegalStateException.class) + .extracting(Throwable::getMessage) + .contains("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) in order to support Reactor Context"); + } + @Test public void monoWhenPermitAllThenAopDoesNotSubscribe() { when(this.delegate.monoFindById(1L)).thenReturn(Mono.from(result)); 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 1948b6d575..9478b401cd 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 @@ -20,6 +20,8 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; public interface ReactiveMessageService { + String notPublisherPreAuthorizeFindById(long id); + Mono monoFindById(long id); Mono monoPreAuthorizeHasRoleFindById(long id); Mono monoPostAuthorizeFindById(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 a8b59ad5d8..99d37a84e8 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 @@ -64,6 +64,9 @@ public class PrePostAdviceReactiveMethodInterceptor implements MethodInterceptor throws Throwable { Method method = invocation.getMethod(); Class returnType = method.getReturnType(); + if (!Publisher.class.isAssignableFrom(returnType)) { + throw new IllegalStateException("The returnType " + returnType + " on " + method + " must return an instance of org.reactivestreams.Publisher (i.e. Mono / Flux) in order to support Reactor Context"); + } Class targetClass = invocation.getThis().getClass(); Collection attributes = this.attributeSource .getAttributes(method, targetClass); diff --git a/docs/manual/src/docs/asciidoc/index.adoc b/docs/manual/src/docs/asciidoc/index.adoc index 96dc398106..6c70b71897 100644 --- a/docs/manual/src/docs/asciidoc/index.adoc +++ b/docs/manual/src/docs/asciidoc/index.adoc @@ -1718,6 +1718,12 @@ For additional information about methods that can be overridden, refer to the `G Spring Security supports method security using https://projectreactor.io/docs/core/release/reference/#context[Reactor's Context] which is setup using `ReactiveSecurityContextHolder`. For example, this demonstrates how to retrieve the currently logged in user's message. +[NOTE] +==== +For this to work the return type of the method must be a `org.reactivestreams.Publisher` (i.e. `Mono`/`Flux`). +This is necessary to integrate with Reactor's `Context`. +==== + [source,java] ---- Authentication authentication = new TestingAuthenticationToken("user", "password", "ROLE_USER");