Spring AOP с Spring Webflux после аутентификации

У меня есть Enabled Spring AOP для моей цели ведения журнала и я использую webflux для точки входа API, все работает нормально, если API не проходит ReactiveAuthenticationManager как мой login API, но в тот момент, когда он должен пройти ReactiveAuthenticationManager, он выдает следующее исключение

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]

мой класс АОП

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());
    }
}

функция аутентификации

    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"))
                });
    }

как я могу заставить мой AOP работать с webflux и как я могу решить проблему?

Я не могу сразу ответить на этот вопрос, так как у меня нет опыта реактивного программирования. Но, может быть, я разберусь, если ты выложишь минимальный репродуктор на GitHub и скинь сюда ссылку. Мне нужно что-то воспроизводимое.

kriegaex 24.04.2023 08:25

я изменил немного онлайн-кода, этого должно быть достаточно, я полагаю github.com/crazycatMyopic/issue-

Akash Jain 24.04.2023 21:06

Вы оказали себе большую услугу, предоставив MCVE. Пожалуйста, продолжайте эту практику. Это помогает членам сообщества воспроизводить ваши проблемы и быстрее давать более качественные ответы. 🙂

kriegaex 25.04.2023 16:02
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
145
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Давайте еще раз прочитаем сообщение об ошибке:

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.

Итак, давайте попробуем добавить аннотацию @Order к нашему аспекту. Но сначала небольшое исследование. Вы можете проверить Javadocs, я просто проверил исходный код:

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();
}

Вывод: порядок по умолчанию LOWEST_PRECEDENCE, т.е. Integer.MAX_VALUE. Поэтому давайте просто назначим более высокий приоритет, то есть что-то меньшее, чем Integer.MAX_VALUE. Для вашего простого примера на GitHub подойдет любой из следующих вариантов: @Order(Integer.MAX_VALUE - 1), @Order(0) или любой другой, который вы считаете подходящим в вашей ситуации. Добавив это к вашему аспектному классу, вызывающая сторона увидит это на консоли:

$ curl -L "http://localhost:8080/login" -H "Authorization: Bearer ghfjhgf"
HI

В журнале сервера Spring будет сказано:

[  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()

Обновление, отвечая на этот дополнительный вопрос:

Я до сих пор удивляюсь, почему так получилось, зачем понадобился @Order? Есть только один совет (из моего АОП), какой еще будет работать?

Если вы удалите @PreAuthorize из метода входа в систему, который возвращает Mono, он будет работать без аннотации @Order к аспекту. Это дает вам подсказку: Spring Security, похоже, также использует АОП или, по крайней мере, какой-то перехватчик методов. Давайте посмотрим на фактическую ошибку более подробно:

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]
(...)

Обратите внимание на org.springframework.security.access.prepost.PrePostAdviceReactiveMethodInterceptor в трассировке стека. Очевидно, что в Spring Security есть специальный метод-перехватчик для реактивных методов. Как описано в сообщении об ошибке, которое вы и я опубликовали ранее, этот перехватчик, похоже, требует особого порядка цепочки перехватчик/совет. Я предполагаю, что это какая-то проблема начальной загрузки, с которой должны смириться пользователи, желающие объединить Spring AOP, Spring Security и WebFlux.

это сработало как шарм, спасибо, но мне все еще интересно, почему это произошло, почему потребовалось @Order? есть только один совет (из моего АОП) какой еще будет работать?

Akash Jain 25.04.2023 20:57

Обратите внимание на мое обновление.

kriegaex 26.04.2023 09:20

спасибо, что рассмотрели мой дополнительный вопрос, это было действительно полезно. 😀

Akash Jain 26.04.2023 21:10

Другие вопросы по теме