From 5a9ddc8c66f135e52b3d9f82a22fdbc2398d4a1a Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Mon, 19 Jan 2026 17:11:41 +0900 Subject: [PATCH] =?UTF-8?q?=EB=8B=A4=EC=9A=B4=EB=A1=9C=EB=93=9C=20?= =?UTF-8?q?=ED=98=B8=EC=B6=9C=20=EB=A1=9C=EA=B7=B8=EA=B4=80=EB=A6=AC=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/FileDownloadInteceptor.java | 89 +++++++++++++++++++ .../kamco/cd/kamcoback/config/WebConfig.java | 14 +++ .../kamcoback/config/api/ApiLogFunction.java | 13 +-- .../postgres/entity/AuditLogEntity.java | 26 +++++- 4 files changed, 136 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java diff --git a/src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java b/src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java new file mode 100644 index 00000000..506d6477 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/config/FileDownloadInteceptor.java @@ -0,0 +1,89 @@ +package com.kamco.cd.kamcoback.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.kamco.cd.kamcoback.auth.CustomUserDetails; +import com.kamco.cd.kamcoback.config.api.ApiLogFunction; +import com.kamco.cd.kamcoback.menu.dto.MenuDto; +import com.kamco.cd.kamcoback.menu.service.MenuService; +import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; +import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Objects; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Slf4j +@Component +public class FileDownloadInteceptor implements HandlerInterceptor { + + private final AuditLogRepository auditLogRepository; + private final MenuService menuService; + + @Autowired private ObjectMapper objectMapper; + + public FileDownloadInteceptor(AuditLogRepository auditLogRepository, MenuService menuService) { + this.auditLogRepository = auditLogRepository; + this.menuService = menuService; + } + + @Override + public void afterCompletion( + HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { + + // 파일 다운로드 API만 필터링 + if (!request.getRequestURI().contains("/download")) { + return; + } + + Long userId = extractUserId(request); + String ip = ApiLogFunction.getClientIp(request); + + List list = menuService.getFindAll(); + List result = + list.stream() + .map( + item -> { + if (item instanceof LinkedHashMap map) { + return objectMapper.convertValue(map, MenuDto.Basic.class); + } else if (item instanceof MenuDto.Basic dto) { + return dto; + } else { + throw new IllegalStateException("Unsupported cache type: " + item.getClass()); + } + }) + .toList(); + + String normalizedUri = request.getRequestURI().replace("/api", ""); + MenuDto.Basic basic = + result.stream() + .filter( + menu -> menu.getMenuUrl() != null && normalizedUri.startsWith(menu.getMenuUrl())) + .max(Comparator.comparingInt(m -> m.getMenuUrl().length())) + .orElse(null); + + AuditLogEntity log = + AuditLogEntity.forFileDownload( + userId, + request.getRequestURI(), + Objects.requireNonNull(basic).getMenuUid(), + ip, + response.getStatus()); + + auditLogRepository.save(log); + } + + private Long extractUserId(HttpServletRequest request) { + if (request.getUserPrincipal() instanceof UsernamePasswordAuthenticationToken auth + && auth.getPrincipal() instanceof CustomUserDetails userDetails) { + return userDetails.getMember().getId(); + } + return null; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java b/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java index 0af7fa2b..afa1aa25 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/WebConfig.java @@ -10,11 +10,18 @@ import org.locationtech.jts.geom.Polygon; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { + private final FileDownloadInteceptor fileDownloadInteceptor; + + public WebConfig(FileDownloadInteceptor fileDownloadInteceptor) { + this.fileDownloadInteceptor = fileDownloadInteceptor; + } + @Bean public ObjectMapper objectMapper() { SimpleModule module = new SimpleModule(); @@ -29,4 +36,11 @@ public class WebConfig implements WebMvcConfigurer { return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build(); } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry + .addInterceptor(fileDownloadInteceptor) + .addPathPatterns("/api/inference/download/**"); // 추론 파일 다운로드 API만 //TODO 추후 추가 + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java index 446c7317..f9b48d09 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java @@ -5,6 +5,7 @@ import com.kamco.cd.kamcoback.log.dto.EventType; import com.kamco.cd.kamcoback.menu.dto.MenuDto; import jakarta.servlet.http.HttpServletRequest; import java.io.UnsupportedEncodingException; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -47,7 +48,7 @@ public class ApiLogFunction { String method = request.getMethod().toUpperCase(); String uri = request.getRequestURI().toLowerCase(); - // URL 기반 DOWNLOAD/PRINT 분류 + // URL 기반 DOWNLOAD/PRINT 분류 -> /download는 FileDownloadInterceptor로 옮김 if (uri.contains("/download") || uri.contains("/export")) { return EventType.DOWNLOAD; } @@ -121,13 +122,15 @@ public class ApiLogFunction { public static String getUriMenuInfo(List menuList, String uri) { - MenuDto.Basic m = + String normalizedUri = uri.replace("/api", ""); + MenuDto.Basic basic = menuList.stream() - .filter(menu -> menu.getMenuApiUrl() != null && uri.contains(menu.getMenuApiUrl())) - .findFirst() + .filter( + menu -> menu.getMenuUrl() != null && normalizedUri.startsWith(menu.getMenuUrl())) + .max(Comparator.comparingInt(m -> m.getMenuUrl().length())) .orElse(null); - return m != null ? m.getMenuUid() : "SYSTEM"; + return basic != null ? basic.getMenuUid() : "SYSTEM"; } public static String cutRequestBody(String value) { diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java index 7fb389f6..b6a18f32 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java @@ -4,7 +4,14 @@ import com.kamco.cd.kamcoback.log.dto.AuditLogDto; import com.kamco.cd.kamcoback.log.dto.EventStatus; import com.kamco.cd.kamcoback.log.dto.EventType; import com.kamco.cd.kamcoback.postgres.CommonCreateEntity; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -14,6 +21,7 @@ import lombok.NoArgsConstructor; @NoArgsConstructor(access = AccessLevel.PROTECTED) @Table(name = "tb_audit_log") public class AuditLogEntity extends CommonCreateEntity { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "audit_log_uid", nullable = false) @@ -62,6 +70,22 @@ public class AuditLogEntity extends CommonCreateEntity { this.errorLogUid = errorLogUid; } + /** 파일 다운로드 이력 생성 */ + public static AuditLogEntity forFileDownload( + Long userId, String requestUri, String menuUid, String ip, int httpStatus) { + + return new AuditLogEntity( + userId, + EventType.DOWNLOAD, // 이벤트 타입 고정 + httpStatus < 400 ? EventStatus.FAILED : EventStatus.SUCCESS, // 성공 여부 + menuUid, + ip, + requestUri, + null, // requestBody 없음 + null // errorLogUid 없음 + ); + } + public AuditLogDto.Basic toDto() { return new AuditLogDto.Basic( this.id,