
📌 Interceptor
Spring의 `HandlerInterceptor`를 활용하여 API 요청 전에 권한을 검사하거나 로그를 남길 때 사용
`HandlerInterceptor`를 구현하여 요청(Request)과 응답(Response)을 가로채는 방식
- 서버에 들어온 Request 객체를 컨트롤러의 핸들러로 도달하기 전에 낚아채서 부가적인 기능이 실행되게끔 만들어준다.
주로 인증/인가 체크 또는 요청 데이터를 사전 처리하는 용도로 활용
- 로그인 체크, 권한 체크, 프로그램 실행시간 작업, 로그확인 등
특징
- 요청과 응답을 직접 가로채고 로깅 가능
- `WebMvcConfigurer`를 구현한 Config 클래스에 등록하여 사용
- 특정 URL 패턴에 대해서만 로깅을 적용할 수 있음
- Controller 진입 전과 후를 제어하기에 적합
- Service 계층 내부의 동작은 감지할 수 없음
주요 메서드
- `preHandle()`: 요청이 Controller에 도달하기 전 실행됨
- `postHandle()`: 요청이 Controller에서 실행된 후, View 렌더링 전 실행됨
- `afterCompletion()`: 요청 처리가 완료된 후 실행됨
Interceptor로 Logging 예시
@Component
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
UserRole userRole = UserRole.valueOf((String) request.getAttribute("userRole"));
if (!UserRole.ADMIN.equals(userRole)) {
log.warn("{} 관리자 권한 없음 - Method: {}, URL: {}", LocalDateTime.now(), request.getMethod(), request.getRequestURI());
response.sendError(HttpServletResponse.SC_FORBIDDEN, "관리자 권한이 없습니다.");
return false;
}
log.info("{} admin 접근 - Method: {}, URL: {}", LocalDateTime.now(), request.getMethod(), request.getRequestURI());
return true;
}
}
📌 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)
`@Aspect`와 `@Around` 등을 활용하여 비지니스 로직과 별개로 공통 기능(로깅, 트랜잭션 관리 등)을 적용할 때 사용
API 실행 전후의 데이터를 수집하여 세부적인 로깅을 구현할 때 유용
메서드 단위의 공통 로직 로깅 시 편리
특징
- 모든 계층에서 로깅 가능 (Service 계층에서도 로깅 가능)
- 코드 변경 없이 로깅 적용 가능
- Interceptor보다 요청과 응답의 직접적인 로깅에는 적합하지 않음
주요 어노테이션
- `@Aspect`: AOP 기능을 가진 클래스임을 명시
- `@Around("execution(패키지.클래스.메서드)")`: 특정 메서드 실행 전후로 코드 실행
- `@Before`: 메서드 실행 전 동작
- `@AfterReturning`: 메서드 실행이 성공적으로 종료된 후 동작
AOP로 Logging 예시
@Aspect
@Component
public class ApiLoggingAspect {
private static final Logger log = LoggerFactory.getLogger(ApiLoggingAspect.class);
private final HttpServletRequest request;
private final ObjectMapper objectMapper;
public ApiLoggingAspect(HttpServletRequest request, ObjectMapper objectMapper) {
this.request = request;
this.objectMapper = objectMapper;
}
@Pointcut("execution(* org.example.expert.domain.comment.controller.CommentAdminController.deleteComment(..)) " +
"|| execution(* org.example.expert.domain.user.controller.UserAdminController.changeUserRole(..))")
private void cut(){}
@Around("cut()")
public Object logAdminApi(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Method method = getMethod(proceedingJoinPoint);
String requestUrl = request.getRequestURI();
String requestMethod = request.getMethod();
Long userId = (Long) request.getAttribute("userId");
log.info("======= [Admin API Request] =======");
log.info("Request User ID: {}", userId);
log.info("Request Time: {}", LocalDateTime.now());
log.info("Request URL: {}", requestUrl);
log.info("Request Method: {}", requestMethod);
log.info("Method Name: {}", method.getName());
Object[] args = proceedingJoinPoint.getArgs();
if (args.length == 0) {
log.info("Request Body: no parameter");
} else {
Arrays.stream(args).forEach(arg -> {
log.info("Request Parameter Type: {}", arg.getClass().getSimpleName());
log.info("Request Parameter: {}", convertToJson(arg));
});
}
// proceed()를 호출하여 실제 메서드 실행
Object returnObj = proceedingJoinPoint.proceed();
log.info("======= [Admin API Response] =======");
if (returnObj != null) {
log.info("Response Type: {}", returnObj.getClass().getSimpleName());
if (returnObj instanceof ResponseEntity) {
ResponseEntity<?> responseEntity = (ResponseEntity<?>) returnObj;
log.info("Response Status: {}", responseEntity.getStatusCode());
log.info("Response Body: {}", convertToJson(responseEntity.getBody()));
} else {
log.info("Response Value: {}", convertToJson(returnObj));
}
} else {
log.info("Response Value: null (void method)");
}
return returnObj;
}
private Method getMethod(ProceedingJoinPoint proceedingJoinPoint) {
MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
return signature.getMethod();
}
private String convertToJson(Object object) {
if (object == null) return "null";
try {
return objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
log.warn("Json 형식으로 변환 실패: {}", e.getMessage());
return "Json 형식으로 변환 실패";
}
}
}
'TIL (Today I Learned)' 카테고리의 다른 글
| SPRING ADVANCED 과제 | ‘내’가 정의한 문제와 해결 과정 기록 📜 (0) | 2025.02.27 |
|---|---|
| Spring Security를 활용한 JWT 인증 적용해보기 🖥️🔑 (0) | 2025.02.27 |
| Spring JWT 인증 과정 뜯어보기 🔑 | Spring Security 없이 구현하는 방법 (0) | 2025.02.25 |
| git clone 후 build.gradle 빨간 줄❓ 해결 방법‼️ (0) | 2025.02.24 |
| [Spring] 인증/인가와 Session 방식과 JWT 방식의 차이 (0) | 2025.02.20 |