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) │