Почему Spring AOP не может использоваться в фильтре Spring Security?

Вопрос или проблема

Я пытаюсь зарегистрировать свое приложение. Поэтому я создал собственную аннотацию aop. Все в порядке. Но когда я добавляю свою аннотацию в фильтр безопасности Spring, у меня возникает ошибка. Я уже искал решение, но не нашел, как это решить. Или как зарегистрировать для безопасности Spring и Restapi.

это моя аннотация

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Logging {
    
}

это мои файлы конфигурации аннотации

@Aspect
@Component
public class LoggingAdvice {

    @Autowired
    private WebClient webClient;
    
    @Around("@within(Logging)")
    public Object applicationLogger(ProceedingJoinPoint point) throws Throwable{
            ObjectMapper mapper = new ObjectMapper();
            Object object = point.proceed();
            String status = null;
            String url = null;
            String message = null;
            String method = null;

            String data = mapper.writeValueAsString(object);
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            Map<String,Object> res = mapper.readValue(data,Map.class);

            String body = mapper.writeValueAsString(res.get("body"));
            status = res.get("statusCodeValue").toString();

            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                url = request.getRequestURL().toString();
                method = request.getMethod();
            }
            if(!status.equals("200")){
                Map<String,Object> bodyData = mapper.readValue(body,Map.class);
                message = bodyData.get("message").toString();
            }
                var log = creatLog("aop annotation",status,url,method,message);
                saveLog(log);
            return object;  
    }

    
   
    @AfterThrowing(pointcut = "@within(Logging)",throwing = "ex")
    public void exceptionLog(Exception ex){
            String status = null;
            String url = null;
            String message = null;
            String method = null;
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            status = "500";
            if (attributes != null) {
                HttpServletRequest request = attributes.getRequest();
                url = request.getRequestURL().toString();
                method = request.getMethod();
            }
                message = ex.getMessage();
                var log = creatLog("exception aop annotation",status,url,method,message);
                saveLog(log);
            
    }





    private void saveLog(Log log){
        try {
            webClient.post()
                         .uri(new URI("http://localhost:8081/log/saveLog"))
                         .body(Mono.just(log),Log.class)
                         .retrieve()
                         .bodyToMono(String.class)
                         .block();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
    }

    private Log creatLog(String name,String statusCode,String url,String method,String message){
        return Log.builder()
                .name(name)
                .message(message)
                .statusCode(statusCode)
                .date(LocalDateTime.now().toString())
                .url(url)
                .method(method)
                .build();
    }

}

это мой фильтр

@Logging
@Component
public class JwtAuthFilter extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private TokenRepo tokenRepo;
    @Autowired
    @Qualifier("handlerExceptionResolver")
    private HandlerExceptionResolver resolver;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        final String authHeader = request.getHeader("Authorization");
        final String jwt;
        final String userEmail;
        try {
            if(authHeader == null || !authHeader.startsWith("Bearer ")){
                filterChain.doFilter(request, response);
                return;
            }
            jwt = authHeader.substring(7);
            userEmail = jwtService.extractUserEmail(jwt);
            if(userEmail != null && SecurityContextHolder.getContext().getAuthentication() == null){
                UserDetails userDetails = userDetailsService.loadUserByUsername(userEmail);
                var isTokenValid = tokenRepo.findByToken(jwt)
                                    .map(token -> !token.isExpired() && !token.isRevoked())
                                    .orElse(false);
                if (jwtService.isTokenValid(jwt, userDetails) && isTokenValid) {
                    UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
                    authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(authToken);
                }
            }
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            response.setStatus(HttpStatus.FORBIDDEN.value());
            response.getWriter().write("invalid token");
            response.getWriter().flush();
            resolver.resolveException(request, response, null, e);
        }
        
    }
    



}

я получил ошибку

2024-10-24T21:56:24.116+06:30  WARN 14820 --- [test] [  restartedMain] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view включено по умолчанию. Поэтому запросы к базе данных могут выполняться во время рендеринга представления. Явно настройте spring.jpa.open-in-view, чтобы отключить это предупреждение
2024-10-24T21:56:24.314+06:30  WARN 14820 --- [test] [  restartedMain] o.s.aop.framework.CglibAopProxy          : Невозможно создать прокси для метода, реализующего интерфейс [public final void org.springframework.web.filter.OncePerRequestFilter.doFilter(jakarta.servlet.ServletRequest,jakarta.servlet.ServletResponse,jakarta.servlet.FilterChain) throws jakarta.servlet.ServletException,java.io.IOException], потому что он помечен как final, рассмотрите возможность использования прокси на основе интерфейса JDK вместо этого.
2024-10-24T21:56:24.319+06:30  WARN 14820 --- [test] [  restartedMain] o.s.aop.framework.CglibAopProxy          : Невозможно создать прокси для метода, реализующего интерфейс [public final void org.springframework.web.filter.GenericFilterBean.init(jakarta.servlet.FilterConfig) throws jakarta.servlet.ServletException], потому что он помечен как final, рассмотрите возможность использования прокси на основе интерфейса JDK вместо этого.
2024-10-24T21:56:24.371+06:30 ERROR 14820 --- [test] [  restartedMain] o.a.c.c.C.[Tomcat-2].[localhost].[/]     : Исключение при запуске фильтра [jwtAuthFilter]

java.lang.NullPointerException: Невозможно вызвать "org.apache.commons.logging.Log.isDebugEnabled()", потому что "this.logger" равен null
        at org.springframework.web.filter.GenericFilterBean.init(GenericFilterBean.java:239) ~[spring-web-6.1.13.jar:6.1.13]
        at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:245) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:102) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:3844) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4448) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1193) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[na:na]
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1203) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
        at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:145) ~[na:na]
        at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:749) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:772) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:164) ~[tomcat-embed-core-10.1.30.jar:10.1.30]
        at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317) ~[na:na]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) ~[na:na]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) ~[na:na]
        at java.base/java.lang.Thread.run(Thread.java:1583)
2024-10-24T21:56:24.387+06:30 ERROR 14820 --- [test] [  restartedMain] o.apache.catalina.core.StandardContext   : Один или несколько фильтров не удалось запустить. Полную информацию можно найти в соответствующем файловом журнале контейнера
2024-10-24T21:56:24.388+06:30 ERROR 14820 --- [test] [  restartedMain] o.apache.catalina.core.StandardContext   : Контекст [] не удалось запустить из-за предыдущих ошибок
2024-10-24T21:56:24.394+06:30  WARN 14820 --- [test] [  restartedMain] o.a.c.loader.WebappClassLoaderBase       : Веб-приложение [ROOT] похоже, что запустило поток с именем [HikariPool-13 housekeeper], но не смогло его остановить. Это очень вероятно создаст утечку памяти. Стек вызовов потока:
 java.base/jdk.internal.misc.Unsafe.park(Native Method)
 java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)
 java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1758)
 java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1182)
 java.base/java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:899)
 java.base/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1070)
 java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
 java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
 java.base/java.lang.Thread.run(Thread.java:1583)
2024-10-24T21:56:24.398+06:30  WARN 14820 --- [test] [  restartedMain] o.a.c.loader.WebappClassLoaderBase       : Веб-приложение [ROOT] похоже, что запустило поток с именем [HikariPool-13 connection adder], но не смогло его остановить. Это очень вероятно создаст утечку памяти. Стек вызовов потока:
 java.base/jdk.internal.misc.Unsafe.park(Native Method)
 java.base/java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:269)
 java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:1758)
 java.base/java.util.concurrent.LinkedBlockingQueue.poll(LinkedBlockingQueue.java:460)
 java.base/java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1069)
 java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
 java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
 java.base/java.lang.Thread.run(Thread.java:1583)
2024-10-24T21:56:24.424+06:30  INFO 14820 --- [test] [  restartedMain] o.apache.catalina.core.StandardService   : Остановка сервиса [Tomcat]
2024-10-24T21:56:24.428+06:30  WARN 14820 --- [test] [  restartedMain] ConfigServletWebServerApplicationContext : При инициализации контекста возникло исключение - отмена попытки обновления: org.springframework.context.ApplicationContextException: Не удалось запустить веб-сервер
2024-10-24T21:56:24.430+06:30  INFO 14820 --- [test] [  restartedMain] j.LocalContainerEntityManagerFactoryBean : Закрытие JPA EntityManagerFactory для единицы сохранения по умолчанию         
2024-10-24T21:56:24.431+06:30  INFO 14820 --- [test] [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-13 - Инициация завершения...
2024-10-24T21:56:24.447+06:30  INFO 14820 --- [test] [  restartedMain] com.zaxxer.hikari.HikariDataSource       : HikariPool-13 - Завершение завершения.
2024-10-24T21:56:24.453+06:30  INFO 14820 --- [test] [  restartedMain] .s.b.a.l.ConditionEvaluationReportLogger : 

Ошибка при запуске ApplicationContext. Чтобы отобразить отчет об оценке условий, перезапустите приложение с включенным 'debug'.
2024-10-24T21:56:24.464+06:30 ERROR 14820 --- [test] [  restartedMain] o.s.boot.SpringApplication               : Запуск приложения завершился неудачей

org.springframework.context.ApplicationContextException: Не удалось запустить веб-сервер
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:165) ~[spring-boot-3.3.4.jar:3.3.4]      
        at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:619) ~[spring-context-6.1.13.jar:6.1.13]
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.3.4.jar:3.3.4]        
        at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.3.4.jar:3.3.4]
        at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.3.4.jar:3.3.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.3.4.jar:3.3.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.3.4.jar:3.3.4]
        at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.3.4.jar:3.3.4]
        at com.test.TestApplication.main(TestApplication.java:12) ~[classes/:na]
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103) ~[na:na]
        at java.base/java.lang.reflect.Method.invoke(Method.java:580) ~[na:na]
        at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:50) ~[spring-boot-devtools-3.3.4.jar:3.3.4]
Caused by: org.springframework.boot.web.server.WebServerException: Не удалось запустить встроенный Tomcat
        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:188) ~[spring-boot-3.3.4.jar:3.3.4]        at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:162) ~[spring-boot-3.3.4.jar:3.3.4]      
        ... 11 общих кадров пропущено
Caused by: java.lang.IllegalStateException: StandardEngine[Tomcat-2].StandardHost[localhost].TomcatEmbeddedContext[] не удалось запустить
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.rethrowDeferredStartupExceptions(TomcatWebServer.rethrowDeferredStartupExceptions(TomcatWebServer.java:209) ~[spring-boot-3.3.4.jar:3.3.4]
        at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:131) ~[spring-boot-3.3.4.jar:3.3.4]
        ... 16 общих кадров пропущено

Ответ или решение

Почему Spring AOP не может быть использован в фильтре безопасности Spring

Вопрос о том, почему Spring AOP не работает должным образом в контексте фильтров безопасности Spring, как, например, ваш JwtAuthFilter, затрагивает ряд важных аспектов, касающихся работы Spring Framework, аспектно-ориентированного программирования и архитектуры веб-приложений. Рассмотрим этот вопрос более детально.

1. Основы Aspect-Oriented Programming (AOP)

Aspect-Oriented Programming — это парадигма программирования, которая позволяет разделить бизнес-логику и кросс-функциональные аспекты, такие как логгирование, безопасность и обработка исключений. Spring предоставляет простую в использовании интеграцию AOP, однако в рамках этого подхода важным является то, где и как применяется прокси.

2. Типы прокси в Spring

Spring AOP использует прокси для внедрения аспектов в компоненты приложения. Важно отметить, что:

  • JDK Dynamic Proxies: Работает с объектами, реализующими интерфейсы. Создание объекта через интерфейс позволяет использовать динамическое прокси.
  • CGLIB Proxy: Используется для классов, которые не реализуют интерфейсы. Прокси создаются на основе подтипирования.

Фильтры в Spring, такие как OncePerRequestFilter, имеют строго определённые методы, которые либо могут быть объявлены как final, либо они не зависят от интерфейсов, что делает их неподходящими для использования с CGLIB.

3. Ограничения фильтров Spring

Фильтры Spring, как и ваш JwtAuthFilter, зависят от определённого цикла жизни servlet. Чтобы Spring AOP корректно работал с прокси, Spring должен вытяну́ть объект на основе контекста, воспользовавшись механизмом времени выполнения, что не всегда возможно в контексте фильтров. Вы сталкиваетесь с ошибками, поскольку:

  • Методы фильтров являются final: Это приводит к тому, что интеграция с AOP становится невозможно, так как нельзя перезаписать эти методы.
  • Инициализация: Ошибка инициализации фильтра в вашем логе (Cannot invoke "org.apache.commons.logging.Log.isDebugEnabled()" because "this.logger" is null) указывает на то, что фильтры не полностью интегрированы с AOP, что может вызвать нарушение контракта.

4. Решение проблемы

Чтобы решать вопрос логирования в фильтрах или обеспечивать безопасность, рекомендуем использовать не AOP, а собственный подход, например:

  • Логирование напрямую в вашем фильтре: Вместо использования аспектов вы можете реализовать логику логирования непосредственно внутри методов doFilterInternal().
  • Передача контекста: Используйте специальный компонент для логирования, который может быть инжектирован в ваш фильтр, вместо того чтобы пытаться применить аннотацию AOP.
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    logRequest(request); // Вызов метода логирования
    try {
        // Ваша логика фильтра
    } catch (Exception e) {
        // Логирование ошибок
    } finally {
        filterChain.doFilter(request, response);
    }
}

5. Заключение

Использование Spring AOP в контексте фильтров безопасности не является возможным по ряду архитектурных причин. Однако целостное понимание архитектуры вашего приложения и использование других подходов, таких как внедрение собственного логирования без AOP, позволит вам получить необходимые результаты. Это не только устранит текущую проблему, но и обеспечит гибкость и масштабируемость вашего приложения в будущем.

Оцените материал
Добавить комментарий

Капча загружается...