I have a Enabled Spring AOP for my logging purpose and use webflux for the API entrypoint,
things work fine if the api does not go through ReactiveAuthenticationManager
like my login
api but the moment It has to go through ReactiveAuthenticationManager
it throws the bellow exception
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.23.jar:5.3.23]
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
*__checkpoint ⇢ Handler com.ums.api.contoller.Controller#getAllUser() [DispatcherHandler]
my AOP class
public class LoggerAspect {
@Pointcut("within(com.ums..*)")
public void logEveryFunction() {
}
@Before(value = "logEveryFunction()")
public void log(JoinPoint joinPoint) {
log.info("Entering function {} in location {}", joinPoint.getSignature(), joinPoint.getSourceLocation());
}
@After(value = "logEveryFunction()")
public void logEnd(JoinPoint joinPoint) {
log.info("Exiting function {}", joinPoint.getSignature());
}
}
authentication function
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.justOrEmpty(authentication.getCredentials().toString())
.map(value -> {
return new UsernamePasswordAuthenticationToken(
authentication.getName(),
null,
Arrays.asList(new SimpleGrantedAuthority("VIEW"))
});
}
how can I make my AOP work with webflux and how can I fix the issue?
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.