Spring Boot 개념 정리(Filter, Interceptor, AOP) + 과제

제이 용·2025년 12월 4일
post-thumbnail

Filter, Interceptor, AOP

사용 이유

  • 모든 요청에 대해 로그를 남기고 싶다
  • 로그인된 사용자만 특정 요청을 허용하고 싶다
  • 요청이 들어올 때마다 요청 시간을 측정하고 싶다
  • 특정 서비스 로직 실행 전후로 공통적인 검증이나 처리를 하고 싶다

다만 이 로직들을 모든 코드에 작성하게 된다면 가독성도 떨어지고 보수하기 굉장히 불편해진다.


특징

항목FilterInterceptorAOP
처리 위치DispatcherServlet 이전컨트롤러 실행 전후Spring Bean 메서드 전후
대상서블릿 요청(Request/Response)핸들러(Controller)모든 Bean 메서드
사용 목적인증, 로깅, CORS 처리 등세션 체크, 권한 검사로깅, 트랜잭션, 예외 처리 등
선언 방법Filter 구현체HandlerInterceptor 구현체@Aspect 클래스

그림으로 이해하기

[사용자 요청]
   ↓
[Filter] ← 요청 시작 시 로그 출력
   ↓
[Interceptor] ← Controller 실행 전/후 로그 출력
   ↓
[AOP] ← Service 메서드 실행 전/후/예외 시점 로그 출력

Filter – 인증 토큰 처리 (ex. JWT)

  • Authorization 헤더에 담긴 JWT 토큰 유효성 검사
  • 모든 요청의 가장 앞단에서 작동해야한다, Spring DispatcherServlet 이전에 실행되어야 한다.
  • 인증 실패 시 컨트롤러까지 가지 않도록 빠르게 차단 해야한다.

Interceptor – 로그인 여부 체크

  • 로그인이 필요한 페이지 요청 시 세션 또는 인증 여부 검증
  • 컨트롤러 실행 직전에 처리 가능하고, 사용자별 세션, 권한 확인 로직 작성에 용이하기 때문.
  • 요청 URL 기반으로 유연하게 경로별 적용 가능하다.
    • 작성자 본인 확인
      ex) 내가 작성한 글만 삭제 가능한 경우
    • 비즈니스 조건
      ex) 유료 회원만 접근

HandlerInterceptor

  • Filter와 마찬가지로 처음부터 다 구현하는 것이 아닌 이미 구현되어 있는 HandlerInterceptor 를 활용한다.

  • 메서드

    • preHandle : 컨트롤러 실행 전에 동작 (false 반환 시 흐름 중단 가능)
    • postHandle : 컨트롤러 실행 후, 뷰 렌더링 전
    • afterCompletion : 응답 완료 후, 예외 처리 포함

구현 방법

@Slf4j
@Component
@RequiredArgsConstructor
public class UserOwnerCheckInterceptor implements HandlerInterceptor {

    private final UserService userService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {

        // 1. 현재 로그인한 사용자 이름 꺼내기
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        String currentUsername = auth.getName();

        // 2. 요청 URI에서 username 추출
        String path = request.getRequestURI();  // /api/user/{username}/email
        String decodedPath = URLDecoder.decode(path, StandardCharsets.UTF_8);

        String[] parts = decodedPath.split("/");
        String username = parts[parts.length - 2];

        // 3. 게시글 작성자와 비교
        if(!currentUsername.equals(username)) {
            log.warn("작성자 아님. 접근 거부");
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "작성자만 수정할 수 있습니다.");
            return false;
        }

        return true;
    }
}
  • 필요 어노테이션

    • Slf4j : 로깅을 사용할 시

    • Component : 빈 등록 필수

    • RequiredArgsConstructor : 속성의 생성자 생성을 위해 사용

  • HandlerInterceptor을 해당 클래스를 구현한다.

  • preHandle 메서드의 매개변수는 HttpServlet 요청/응답, Object handler 이고 예외를던 져준다.

  • 해당 리퀘스트를 통해 내부 사용자의 정보를 꺼내 원하는 조건문을 만들어 메서드를 완성 시킨다.

  • 완성된 인터셉터를 WebConfig에 등록을 시켜준다.

@Configuration
@RequiredArgsConstructor
public class WebMvcConfig implements WebMvcConfigurer {

    private final UserOwnerCheckInterceptor userOwnerCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(userOwnerCheckInterceptor)
            .addPathPatterns("/api/user/**/email");
    }
}

저번에 다뤘던 내용처럼 빈으로 등록만 해주는 것이 아닌 인터셉터를 추가를 해주어야만 인식을 하기 때문에 필수 과정이다.


AOP – 실행 시간 측정 / 로깅

  • Service 계층에서 비즈니스 로직 실행 시간 측정이 필요할 때.
  • 특정 메서드 호출 전후에만 부가 기능을 넣고 싶을 때 유리
  • 핵심 로직은 건드리지 않고 공통 처리만 분리 가능
  • 다양한 메서드에 어노테이션으로 간편하게 확장 가능

그림으로 이해하기

  • 다음과 같이 도메인별 서비스 단에 전부 로깅을 추가하기에는 유지보수하기 매우 부적절 하므로 AOP를 통해 일괄적용함에 따라 유지보수성이 굉장히 좋아짐을 확인할 수 있다.

용어 정리

  • Aspect : 공통 관심사를 담는 모듈 (ex. 로깅, 트랜잭션, 권한 체크)
  • JoinPoint : 메서드 실행 지점
  • Advice : 실행 시점에 따라 작동하는 AOP 로직 (@Before, @After, @Around)
  • Pointcut : 어떤 JoinPoint에 Advice를 적용할지 정의 (표현식 기반)
  • Weaving : Advice를 적용하는 과정 (직접 Weaving을 다룰 일은 없다. 스프링이 해줌)

작성 꿀팁

  • 어떤 것을
    • PointCut 을 통해서 대상을 지정해준다.
  • 언제
    • Advice를 통해서 실행되는 시점을 지정해준다.
  • 어떻게
    • Aspect에 정의한 메서드를 통해서 어떻게 할 것인지 지정해준다.

주 어노테이션

  • @Aspect : Aspect 클래스임을 선언
  • @Around : 메서드 실행 전후 제어 (가장 유연)
  • @Before : 메서드 실행 전에 수행
  • @AfterReturning : 정상 반환 후 수행
  • @AfterThrowing : 예외 발생 시 수행
  • @After : 정상/예외 관계없이 실행 후 수행

활용법

  • 로깅 : 모든 서비스 실행 시 로그 기록
  • 트랜잭션 : 메서드 단위 트랜잭션 처리
  • 권한 검사 : 관리자 권한 체크
  • 예외 처리 : 공통 에러 메시지 처리
  • 실행 시간 측정 : 성능 모니터링

구현 방법

package org.example.nbcam_addvanced_1.common.aspect;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
@Slf4j
public class TimeCheckAop {

    @Around("execution(* org.example.nbcam_addvanced_1.user.service..*(..))")
    public Object executionTime(ProceedingJoinPoint joinPoint) throws Throwable {

        long start = System.currentTimeMillis();

        Object result = joinPoint.proceed(); // 실제 메서드 실행 -> Filter에서 doFilter 와 비슷함.

        long end = System.currentTimeMillis();
        log.info("[AOP] {} 실행됨 in {}ms" , joinPoint.getSignature() , end - start);

        return result;
    }
}
  • @Aspect : AOP를 사용할 클래스다! 지정

  • @Component : 마찬가지로 빈 등록 필수

  • Slf4j : 로깅을 사용할 경우 필수

  • @Around("execution( org.example.nbcam_addvanced_1.user.service..(..))")

    • 어노테이션("") 안에는 내가 적용시킬 메서드들이 위치한 파일의 경로를 입력해주면 된다.
    • * = 와일드카드 모든~ 이라는 뜻
  • JoinPoint를 매개변수로 받아준다.

  • System.currentTimeMillis(); = 현재 시간을 가져오는 기능


마무리

과제를 진행하게 되면서 테스트코드라는 것을 올바르게 작동하도록 수정하는 작업을 하였는데, 틀린 것을 찾고 수정하는 것은 매우 쉬웠지만, 저 코드를 테스트할 코드를 작성하는 것이 굉장히 어려울 것이라고 생각이 들어 좀처럼 손이 가지 않았다.. 튜터님께서 말하신 만큼 중요한 요소라 꼭 도전은 해야겠다만, 지금은 조금 버거워 후에 미뤄볼까 한다.. 절대 귀찮아서가 아니다ㅎㅎ..

또한 배운것을 토대로 인터셉터와 AOP를 구현하는데 손쉽게 따라할 수 있었지만 데이터를 뽑아와 새로운 로직을 생성하는 부분이 재밌으면서도 골이 조금 아팠다. 더 열심히 해야겠당

2개의 댓글

comment-user-thumbnail
2025년 12월 4일

ㅋㅣㅂㅗㄷㅡㄱㅏ ㅇㅗㅐ ㅇㅣㄹㅓㅎㄱㅔ ㅊㅕㅈㅣㅈㅣ?

1개의 답글