From cf958622bd6b01a1d382cc72ddf188af9e970429 Mon Sep 17 00:00:00 2001 From: jade Date: Mon, 3 Aug 2026 13:45:16 +0900 Subject: [PATCH] =?UTF-8?q?feat(security):=20ToolSecurityContext=20?= =?UTF-8?q?=EB=B0=8F=20KMS=20=EB=B3=B5=ED=98=B8=ED=99=94=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EB=AA=A8=EB=93=88=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dap/lib/security/ShinhanKmsService.java | 19 ++++++ .../lib/security/ShinhanKmsServiceImpl.java | 28 ++++++++ .../dap/lib/security/ToolSecurityContext.java | 68 +++++++++++++++++++ .../impl/DailyQuoteToolUseCaseImpl.java | 10 +++ 4 files changed, 125 insertions(+) create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsService.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsServiceImpl.java create mode 100644 dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ToolSecurityContext.java diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsService.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsService.java new file mode 100644 index 00000000..dd989f0b --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsService.java @@ -0,0 +1,19 @@ +package io.shinhanlife.dap.lib.security; + +/** + * 신한라이프 KMS (Key Management System) 암복호화 연동 서비스 인터페이스. + * + * Tool 개발자가 HTTP Header로 전달받은 암호화된 사번(employee-id 등)을 + * 평문으로 복호화하기 위해 사용합니다. + */ +public interface ShinhanKmsService { + + /** + * KMS를 통해 암호화된 문자열을 평문으로 복호화합니다. + * + * @param encryptedText 암호화된 텍스트 + * @return 복호화된 평문 + * @throws RuntimeException 복호화 실패 시 + */ + String decrypt(String encryptedText); +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsServiceImpl.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsServiceImpl.java new file mode 100644 index 00000000..000e6144 --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ShinhanKmsServiceImpl.java @@ -0,0 +1,28 @@ +package io.shinhanlife.dap.lib.security; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +/** + * 신한라이프 KMS (Key Management System) 암복호화 연동 서비스 임시(Mock) 구현체. + * 실제 사내 KMS Jar 모듈이 연동되기 전까지 동작할 수 있도록 구성됨. + */ +@Slf4j +@Service +public class ShinhanKmsServiceImpl implements ShinhanKmsService { + + @Override + public String decrypt(String encryptedText) { + if (encryptedText == null || encryptedText.isEmpty()) { + log.warn("[KMS] 복호화 요청된 텍스트가 비어있습니다."); + return null; + } + + // TODO: 향후 실제 신한라이프 사내 KMS API로 교체 필요 + // 현재는 개발 및 테스트를 위해 입력받은 값을 그대로(혹은 간단한 임시 규칙으로) 반환합니다. + log.debug("[KMS] 복호화 실행 (Mock) - 원본 텍스트: {}", encryptedText); + + // 만약 암호화 텍스트가 특정 패턴을 가지지 않으면 그대로 평문이라 가정하고 반환 (테스트 편의) + return encryptedText; + } +} diff --git a/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ToolSecurityContext.java b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ToolSecurityContext.java new file mode 100644 index 00000000..541b8f1e --- /dev/null +++ b/dap-tool-core/src/main/java/io/shinhanlife/dap/lib/security/ToolSecurityContext.java @@ -0,0 +1,68 @@ +package io.shinhanlife.dap.lib.security; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +/** + * Tool 비즈니스 로직(UseCase) 내부에서 HTTP Request 컨텍스트 및 보안 정보에 + * 쉽게 접근할 수 있도록 돕는 공통 유틸리티 클래스. + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class ToolSecurityContext { + + private final ShinhanKmsService kmsService; + + // 헤더 키 상수는 사내 표준에 맞춰 변경 가능 + private static final String HEADER_EMPLOYEE_ID = "employee-id"; + private static final String HEADER_EMPLOYEE_NO = "employee-no"; + private static final String HEADER_VIRTUAL_EMPLOYEE_NO = "virtual-employee-no"; + + /** + * 현재 스레드(Request Context)에서 실행 중인 사용자의 평문 사번을 획득합니다. + * + * @return 복호화된 사번 (없거나 실패 시 null) + */ + public String getCurrentUserId() { + HttpServletRequest request = getCurrentRequest(); + if (request == null) { + log.warn("[ToolSecurityContext] 현재 활성화된 HTTP 요청 컨텍스트가 없습니다."); + return null; + } + + // 헤더에서 암호화된 사번 정보 추출 (우선순위 고려) + String encryptedUserId = request.getHeader(HEADER_EMPLOYEE_NO); + if (encryptedUserId == null || encryptedUserId.isEmpty()) { + encryptedUserId = request.getHeader(HEADER_EMPLOYEE_ID); + } + if (encryptedUserId == null || encryptedUserId.isEmpty()) { + encryptedUserId = request.getHeader(HEADER_VIRTUAL_EMPLOYEE_NO); + } + + if (encryptedUserId == null || encryptedUserId.isEmpty()) { + log.debug("[ToolSecurityContext] 요청 헤더에 사번 관련 정보가 존재하지 않습니다."); + return null; + } + + // KMS 서비스를 통한 복호화 수행 + try { + return kmsService.decrypt(encryptedUserId); + } catch (Exception e) { + log.error("[ToolSecurityContext] 사번 복호화 실패: {}", e.getMessage(), e); + return null; + } + } + + /** + * 현재 스레드에 바인딩된 HttpServletRequest 객체를 반환합니다. + */ + private HttpServletRequest getCurrentRequest() { + ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); + return (attrs != null) ? attrs.getRequest() : null; + } +} diff --git a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java index 36059326..b99fd67c 100644 --- a/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java +++ b/dap-tool-oth/src/main/java/io/shinhanlife/dap/mcc/biz/smp/usecase/impl/DailyQuoteToolUseCaseImpl.java @@ -4,6 +4,8 @@ import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteRequest; import io.shinhanlife.dap.mcc.biz.smp.dto.DailyQuoteResponse; import io.shinhanlife.dap.mcc.biz.smp.usecase.DailyQuoteToolUseCase; import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase; +import io.shinhanlife.dap.lib.security.ToolSecurityContext; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -26,8 +28,11 @@ import java.util.Random; */ @Slf4j @Service +@RequiredArgsConstructor public class DailyQuoteToolUseCaseImpl extends AbstractMcpToolUseCase implements DailyQuoteToolUseCase { + private final ToolSecurityContext securityContext; + private final List quotes = List.of( new DailyQuoteResponse("성공은 매일 반복한 작은 노력들의 합이다.", "로버트 콜리어"), new DailyQuoteResponse("시작이 반이다.", "아리스토텔레스"), @@ -37,6 +42,11 @@ public class DailyQuoteToolUseCaseImpl extends AbstractMcpToolUseCase implements @Override public DailyQuoteResponse execute(DailyQuoteRequest req) { + // ===================================================================== + // 💡 4. 단 한 줄로 헤더에서 사번을 추출하고 KMS 복호화까지 완료된 값 꺼내기! + String userId = securityContext.getCurrentUserId(); + // ===================================================================== + int index = new Random().nextInt(quotes.size()); DailyQuoteResponse selected = quotes.get(index);