feat(security): ToolSecurityContext 및 KMS 복호화 공통 모듈 추가
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 2m18s
All checks were successful
Deploy to OCIWP / deploy (push) Successful in 2m18s
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.dto.DailyQuoteResponse;
|
||||||
import io.shinhanlife.dap.mcc.biz.smp.usecase.DailyQuoteToolUseCase;
|
import io.shinhanlife.dap.mcc.biz.smp.usecase.DailyQuoteToolUseCase;
|
||||||
import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase;
|
import io.shinhanlife.dap.mcc.usecase.AbstractMcpToolUseCase;
|
||||||
|
import io.shinhanlife.dap.lib.security.ToolSecurityContext;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@@ -26,8 +28,11 @@ import java.util.Random;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
public class DailyQuoteToolUseCaseImpl extends AbstractMcpToolUseCase implements DailyQuoteToolUseCase {
|
public class DailyQuoteToolUseCaseImpl extends AbstractMcpToolUseCase implements DailyQuoteToolUseCase {
|
||||||
|
|
||||||
|
private final ToolSecurityContext securityContext;
|
||||||
|
|
||||||
private final List<DailyQuoteResponse> quotes = List.of(
|
private final List<DailyQuoteResponse> quotes = List.of(
|
||||||
new DailyQuoteResponse("성공은 매일 반복한 작은 노력들의 합이다.", "로버트 콜리어"),
|
new DailyQuoteResponse("성공은 매일 반복한 작은 노력들의 합이다.", "로버트 콜리어"),
|
||||||
new DailyQuoteResponse("시작이 반이다.", "아리스토텔레스"),
|
new DailyQuoteResponse("시작이 반이다.", "아리스토텔레스"),
|
||||||
@@ -37,6 +42,11 @@ public class DailyQuoteToolUseCaseImpl extends AbstractMcpToolUseCase implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public DailyQuoteResponse execute(DailyQuoteRequest req) {
|
public DailyQuoteResponse execute(DailyQuoteRequest req) {
|
||||||
|
// =====================================================================
|
||||||
|
// 💡 4. 단 한 줄로 헤더에서 사번을 추출하고 KMS 복호화까지 완료된 값 꺼내기!
|
||||||
|
String userId = securityContext.getCurrentUserId();
|
||||||
|
// =====================================================================
|
||||||
|
|
||||||
int index = new Random().nextInt(quotes.size());
|
int index = new Random().nextInt(quotes.size());
|
||||||
DailyQuoteResponse selected = quotes.get(index);
|
DailyQuoteResponse selected = quotes.get(index);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user