0

I'd like to use method security annotations and a custom expression evaluator in my Spring Boot 3 Webflux application, and I'm not sure how to do it.

My endpoint with its method security annotation looks like this:

@GetMethod
@PreAuthorize(@foo.canAccess())
public Mono<FooResponse> computeFoos(Mono<FooRequest> fooRequest) {
    ...
}

My bean that handles this expression looks like this:

@Component("foo")
public class FooSecurityExpressionEvaluator {

    public Mono<Boolean> canAccess() {
        return ReactiveSecurityContextHolder.getContext()
                       .map(p -> true);
    }
}

I see an example in the Spring Security documentation that uses a Function declared as a bean. Can I do something similar with a bean that contains multiple methods?

Stack trace is as follows when I try to use my custom evaluator:

│ java.lang.IllegalStateException: No MethodInvocation found: Check that an AOP invocation is in progress and that the ExposeInvocationInterceptor is upfront in the interceptor chain. Specifically, note that advices with order HIGHEST_PRECEDENCE will │
│     at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74)                                                                                                                            │
│     Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:                                                                                                                                                                               │
│ Error has been observed at the following site(s):                                                                                                                                                                                                        │
│     *__checkpoint ⇢ Handler com.foo.FooController#computeFoos(Mono, ServerWebExchange) [DispatcherHandler]                                                                                                            │
│     *__checkpoint ⇢ org.springframework.security.web.server.authorization.AuthorizationWebFilter [DefaultWebFilterChain]                                                                                                                                 │
│     *__checkpoint ⇢ org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter [DefaultWebFilterChain]                                                                                                                          │
│     *__checkpoint ⇢ org.springframework.security.web.server.authentication.logout.LogoutWebFilter [DefaultWebFilterChain]                                                                                                                                │
│     *__checkpoint ⇢ org.springframework.security.web.server.savedrequest.ServerRequestCacheWebFilter [DefaultWebFilterChain]                                                                                                                             │
│     *__checkpoint ⇢ org.springframework.security.web.server.context.SecurityContextServerWebExchangeWebFilter [DefaultWebFilterChain]                                                                                                                    │
│     *__checkpoint ⇢ org.springframework.security.web.server.authentication.AuthenticationWebFilter [DefaultWebFilterChain]                                                                                                                               │
│     *__checkpoint ⇢ org.springframework.security.web.server.context.ReactorContextWebFilter [DefaultWebFilterChain]                                                                                                                                      │
│     *__checkpoint ⇢ org.springframework.security.web.server.header.HttpHeaderWriterWebFilter [DefaultWebFilterChain]                                                                                                                                     │
│     *__checkpoint ⇢ org.springframework.security.config.web.server.ServerHttpSecurity$ServerWebExchangeReactorContextWebFilter [DefaultWebFilterChain]                                                                                                   │
│     *__checkpoint ⇢ org.springframework.security.web.server.WebFilterChainProxy [DefaultWebFilterChain]                                                                                                                                                  │
│     *__checkpoint ⇢ org.springframework.web.filter.reactive.ServerHttpObservationFilter [DefaultWebFilterChain]                                                                                                                                          │
│     *__checkpoint ⇢ HTTP POST "/org" [ExceptionHandlingWebHandler]                                                                                                                                                                                       │
│ Original Stack Trace:                                                                                                                                                                                                                                    │
│         at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74)                                                                                                                        │
│         at org.springframework.aop.aspectj.AbstractAspectJAdvice.getJoinPointMatch(AbstractAspectJAdvice.java:655)                                                                                                                                       │
│         at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44)                                                                                                                                           │
│         at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:57)                                                                                                                         │
│         at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:173)                                                                                                                                     │
│         at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)                                                                                                                                         │
│         at org.springframework.security.authorization.method.ReactiveMethodInvocationUtils.proceed(ReactiveMethodInvocationUtils.java:32)                                                                                                                │
│         at org.springframework.security.authorization.method.AuthorizationManagerBeforeReactiveMethodInterceptor.lambda$invoke$2(AuthorizationManagerBeforeReactiveMethodInterceptor.java:113)                                                           │
│         at reactor.core.publisher.MonoDefer.subscribe(MonoDefer.java:44)                                                                                                                                                                                 │
Mark
  • 4,970
  • 5
  • 42
  • 66
  • I also get the same error when I convert to using the example that the Spring Security documentation uses. Note that the documentation references Spring Security 5.8. I'm on 6.0. Looks like the documentation needs to be updated? – Mark Mar 22 '23 at 18:55

1 Answers1

0

The issue was that I had another Aspect intercepting my computeFoos method. Once I removed that, this security check works as described in the question.

I tried to adjust the ordering of the interceptors (I set the @EnableReactiveMethodSecurity(order = Ordered.HIGHEST_PRECEDENCE)) but that didn't seem to work.

Mark
  • 4,970
  • 5
  • 42
  • 66