20240419_TS
20240419_TS
20240419_TS
상황
- 마스킹 처리를 위해서 어노테이션을 커스텀 하여 model에 선언하면 해당 변수만 선언된 타입에 따라서 마스킹 처리가 되는 로직을 개발함
- ObjectMapping에 마스킹하는 모듈을 등록하여 사용하기로 개발 방향을 잡음
- 기존에 설정된 ObjectMapping이 초기화 되는 현상이 발생
경과
1
2
3
4
5
6
7
8
9
10
11
12
public class MaskingSample{
@Masked(type = MaskingType.NAME)
private String name;
@Masked(type = MaskingType.CONTACT)
private String telNo;
@Masked(type = MaskingType.ADDRESS)
private String dtlAddr;
private String region;
}
위에 처럼 선언하면 name, tellNo, dtlAddr 의 값만 선언된 마스킹 규칙에 따라 마스킹 처리가 진행됨
- 커스텀 어노테이션 등록 ```java /**
마스킹 관련 커스텀 어노테이션 클래스
*/
@Target({ElementType.ANNOTATION_TYPE, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME) // 어노테이션 정보가 실행 시간까지 유지되도록 지정
public @interface Masked {
MaskingType type();
} ```- 스프링 ObjectMapper에 마스킹 직렬화 클래스 모듈 등록
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Configuration public class CustomObjectMapperConfig { @Bean public Jackson2ObjectMapperBuilderCustomizer customObjectMapper() { return builder -> { // 기존의 ObjectMapper 가져오기 ObjectMapper objectMapper = builder.build(); // 기존 ObjectMapper에 새로운 모듈을 추가 SimpleModule module = new SimpleModule(); module.addSerializer(String.class, new StringPropertyMasker()); builder.modules(module); }; } }
원인
** 여기서 ObjectMapper에 커스텀 모듈을 등록하면 기존에 등록되어 있던 모듈이 초기화 되는 현상이 발생 **
위처럼 직접 빈 생성을 해주면 그 사이에 설정들을 포함할 수 있다. 이 방식을 사용할 경우 애플리케이션 초기 개발단계라면 큰 문제가 없을 수 있지만 이미 운영 중인 애플리케이션이라면 문제가 발생할 여지가 많다.
spring boot 가 자동으로 생성하는 ObjectMapper 빈은 모든 설정이 초기화된 기본 인스턴스가 아니라 spring boot 에서 이미 다양한 설정들을 해놓은 인스턴스이기 때문이다.
개발자가 직접 ObjectMapper 빈을 생성하는 순간 spring boot 는 더 이상 자동 구성되는 ObjectMapper 빈을 생성하지 않는데 그러면 애플리케이션 전역 ObjectMapper 빈이 직접 생성한 인스턴스로 변경되고, 자동 구성되는 설정들을 알게 모르게 이용하고 있었다면 이 설정들이 모두 바뀌게 되는 문제가 발생하기 때문이다.
출처: https://multifrontgarden.tistory.com/300 [우리집앞마당:티스토리]
위에 내용을 참고하여 디버깅을 해본 결과 builder 에 있는 모듈이 초기화되고 새로 등록한 모듈만이 등록되어 있음을 확인을 함
테스트를 했을 당시 큰 오류는 없었지만 알지 못하는 곳에서 사용 될 수도 있다는 생각이 들어 다른 방식으로 구현하기로 결정했음.
조치
** reflect 클래스를 활용 ** reflect.field() 함수를 통해 등록된 어노테이션에 따라 마스킹 로직을 분기처리함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Mask<T> {
private final MaskingMetaData maskingMetaData;
@Getter
private final T data;
public Mask(T data, Class<T> type) {
this.maskingMetaData = new MaskingMetaData(type);
this.data = data;
mastDataFields();
}
private void mastDataFields(){
for (String fieldName : maskingMetaData.getMaskingTypes()) {
MaskingType maskingType = maskingMetaData.getMaskingType(fieldName);
try {
Field field = data.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
Object fieldValue = field.get(data);
if (fieldValue != null) {
String maskedValue = MaskingUtil.maskingOf(maskingType, fieldValue.toString());
field.set(data, maskedValue);
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException("Error masking data field: " + e.getMessage());
}
}
}
}
메타데이터에 어노테이션와 변수를 HashMap 형태로 저장을 하여 Mask 생성자 함수에서 호출하여 타입에 따라 마스킹 처리를 함
위에 방식대로 하면 @Congifure 클래스, ObjectMapper 를 변경할 필요가 없다는 장점이 있음
ObjectMapper 에서 등록된 모듈과 충돌 가능성이 줄어든다는 장점도 있음
마스킹 메서드 호출 방법
1
2
3
Mask<MaskingInfo> mask = new Mask<>(obj, MaskingInfo.class);
return mask.getData();
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.