Let us read the error message again:
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 execute
before ExposeInvocationInterceptor! In addition, ExposeInvocationInterceptor
and ExposeInvocationInterceptor.currentInvocation() must be invoked from the
same thread.
So let us try by adding an @Order
annotation to our aspect. But first, some research. You can check the Javadocs, I simply checked the source code:
public @interface Order {
int value() default Ordered.LOWEST_PRECEDENCE;
}
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
Conclusion: The default order is LOWEST_PRECEDENCE
, i.e. Integer.MAX_VALUE
. Therefore, let us just assign a higher precedence, i.e. something smaller than Integer.MAX_VALUE
. For your simple example on GitHub, any of the following will do: @Order(Integer.MAX_VALUE - 1)
, @Order(0)
or whatever you determine to be appropriate in your situation. Having added that to your aspect class, the caller will see this on the console:
$ curl -L "http://localhost:8080/login" -H "Authorization: Bearer ghfjhgf"
HI
The Spring server log will say:
[ restartedMain] c.a.s.SpringBootWebfluxJjwtApplication : Started SpringBootWebfluxJjwtApplication in 1.646 seconds (JVM running for 2.156)
[ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state LivenessState changed to CORRECT
[ restartedMain] o.s.b.a.ApplicationAvailabilityBean : Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
...
[oundedElastic-1] c.a.s.rest.LoggerAspect : Entering function Mono com.ard333.springbootwebfluxjjwt.rest.AuthenticationREST.login() in location org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint$SourceLocationImpl@641fcca3
[oundedElastic-1] c.a.s.rest.LoggerAspect : Exiting function Mono com.ard333.springbootwebfluxjjwt.rest.AuthenticationREST.login()
Update, responding to this follow-up question:
I still wonder why did it happen, why was @Order
required? There is only one advice (from my AOP) which other would be running?
If you remove @PreAuthorize
from your login method that returns a Mono
, it works without the @Order
annotation on the aspect. That gives you a clue: Spring Security also seems to use AOP or at least some kind of method interceptor. Let us look at the actual error in more detail:
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 execute before ExposeInvocationInterceptor! In addition, ExposeInvocationInterceptor and ExposeInvocationInterceptor.currentInvocation() must be invoked from the same thread.
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74) ~[spring-aop-5.3.7.jar:5.3.7]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
|_ checkpoint ⇢ Handler com.ard333.springbootwebfluxjjwt.rest.AuthenticationREST#login() [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 ⇢ HTTP GET "/login" [ExceptionHandlingWebHandler]
Stack trace:
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.currentInvocation(ExposeInvocationInterceptor.java:74) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.aspectj.AbstractAspectJAdvice.getJoinPointMatch(AbstractAspectJAdvice.java:658) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.aspectj.AspectJMethodBeforeAdvice.before(AspectJMethodBeforeAdvice.java:44) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:57) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) ~[spring-aop-5.3.7.jar:5.3.7]
at org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor.proceed(PrePostAdviceReactiveMethodInterceptor.java:156) ~[spring-security-core-5.5.0.jar:5.5.0]
at org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor.lambda$invoke$4(PrePostAdviceReactiveMethodInterceptor.java:116) ~[spring-security-core-5.5.0.jar:5.5.0]
at reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:125) ~[reactor-core-3.4.6.jar:3.4.6]
(...)
Please note org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor
in the stacktrace. Obviously, in Spring Security there is a special method interceptor for reactive methods. As described in the error message you and I posted before, this interceptor seems to require a special ordering of the interceptor/advice chain. I guess, it is some kind of bootstrapping problem, which users who wish to combine Spring AOP, Spring Security and WebFlux need to accommodate to.