[TS] 민감 정보 관리를 위한 @SecureKey 커스텀 어노테이션 도입기

정찬's avatar
Jan 25, 2025
[TS] 민감 정보 관리를 위한 @SecureKey 커스텀 어노테이션 도입기

개요

 
Nhn24Store 서비스를 개발하면서 DB 접속 정보, 이메일 인증 키, 외부 API 키 등 다양한 민감 정보를 다뤄야 했습니다.
이런 정보들을 안전하게 관리하기 위해 NHN Cloud의 SKM(Secure Key Manager) 서비스를 도입했지만,
그 과정에서 비즈니스 로직과 설정 로직 간의 결합이라는 구조적 문제가 발생했습니다.
이 글에서는 해당 문제를 어떻게 해결했는지, 그리고 어떻게 어노테이션을 통해 개선했는지를 공유합니다.
 

 

📌 문제 배경

 
처음에는 SecureKeyManagerService를 직접 주입받고, 애플리케이션이 시작될 때 민감 정보를 로드하는 구조였습니다.
 
notion image
이 접근 방식은 민감 정보를 노출하지 않으면서도 안전하게 로드할 수 있다는 장점이 있었지만, 아래와 같은 문제점이 존재했습니다.

❗ 구조적 문제

  • 설정 클래스가 외부 서비스(SKM)의 세부 구현에 직접 의
  • SecureKeyManagerService를 사용하는 곳이 늘어나면서 결합도 증가
  • 비즈니스 로직과 설정 로직이 뒤섞이며 유지보수 어려움
예를 들어, 이메일 발송 서비스에서도 민감 정보(SMTP 계정 정보)를 사용해야 했고, 이 역시 매번 SecureKeyManagerService를 직접 사용해야 했습니다. 이로 인해 설정 변경이 있을 때마다 비즈니스 로직에 영향을 주는 구조가 되었고, 이는 분명 개선이 필요한 지점이었습니다.

🛠️ 해결 방안: @SecureKey 커스텀 어노테이션 도입

이 문제를 해결하기 위해 Spring의 DI(Dependency Injection) 철학에서 아이디어를 얻었습니다.
"객체는 자신이 필요한 의존성을 직접 생성하거나 탐색하지 않고, 외부에서 주입받는다."
이 철학을 민감 정보에도 그대로 적용해보기로 했습니다.
@Service @RequiredArgsConstructor public class EmailSendServiceImpl implements EmailSendService { @SecureKey("secret.keys.email.account") private String USERNAME; @SecureKey("secret.keys.email.password") private String PASSWORD; private final Session EMAIL_SESSION = createEmailSession(); }
이제 SecureKeyManagerService는 내부 구현으로 완전히 은닉되었고, 필요한 클래스에서는 어노테이션만 붙이면 해당 민감 정보를 주입받을 수 있게 되었습니다.

🔧 어떻게 동작하는가?

1. @SecureKey 어노테이션 정의

@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface SecureKey { String value();

2. BeanPostProcessor를 이용해 어노테이션 처리

@Component public class SecureKeyInjector implements BeanPostProcessor { @Autowired private SecureKeyManagerService secureKeyManagerService; @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { Field[] fields = bean.getClass().getDeclaredFields(); for (Field field : fields) { if (field.isAnnotationPresent(SecureKey.class)) { SecureKey annotation = field.getAnnotation(SecureKey.class); String key = annotation.value(); String value = secureKeyManagerService.fetchSecretFromKeyManager(key); field.setAccessible(true); try { field.set(bean, value); } catch (IllegalAccessException e) { throw new RuntimeException("Failed to inject secure key: " + key, e); } } } return bean; } }
이 방식은 Spring Bean이 초기화될 때 필드에 해당 키 값을 자동으로 주입해주며, 개발자는 더 이상 SKM을 직접 다룰 필요가 없습니다.

✅ 적용 결과

  • 민감 정보 로딩 로직 제거 → 비즈니스 로직이 간결해짐
  • SecureKeyManagerService 은닉 → 클래스 간 결합도 감소
  • 설정과 비즈니스 로직의 완전한 분리 → 응집도 향상, 유지보수성 개선
  • 어디서든 동일한 방식으로 민감 정보 사용 가능 → 일관성과 확장성 확보

💡 배운점

 
이 경험을 통해 Spring의 철학은 단순한 프레임워크 문법이 아닌, 아키텍처 설계의 방향성을 제시한다는 것을 느꼈습니다. 그래서 Spring을 공부한다는 것은 단순히 기능을 사용하는 것을 넘어, 느슨한 결합과 높은 응집도를 지향하는 설계 원칙을 체화하는 과정이라고 생각하게 되었습니다.
또한 반복적인 설정 로직을 어노테이션 기반 추상화로 간결하게 만들 수 있다는 것을 배웠습니다. 이를 통해 코드의 가독성과 재사용성이 높아지고, 유지보수에 드는 비용을 줄일 수 있다는 점에서 생산성과 안정성 모두에 기여할 수 있음을 깨달았습니다.
 
 
 
Share article

lushlife99