enableDefaultTyping 과 주의점
enableDefaultTyping
Jackson 이 직렬/역직렬 화 시 타입 정보 자동으로 포함하게 해주는 것
objectMapper.copy()
.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);- DefaultTyping.NON_FINAL : final 이 아닌 모든 클래스에 대해 타입 정보 포함
다형성을 위해 타입이 필요하다. - 어떤 클래스인지 추적 및 JSON 기반 객체 복구
String, Integer, Long 같은 final 클래스들은 타입을 포함시키지 않는다.
-> final 클래스는 서브 클래스가 존재할 수 없으므로 타입 정보가 필요없다.
- JsonTypeInfo.As.PROPERTY : JSON 객체 내부에 프로퍼티로 삽입
{ "@class": "com.example.MyDto", "name": "test", "value": 123 }@class 가 기본값으로 명시된다.
주의점
final 클래스는 저장하지 않으므로, 주의해서 사용해야 하는 부분이 있다.
래퍼 클래스 Caching
public Long getLatestSnapshotId(long provisionId) {
var optionalValue = ...;
if (optionalValue.isEmpty()) {
return null;
}
return optionalValue.get().getId();
}와 같은 코드를 짠다면?
-> 캐시를 호출할 때 에러가 발생하게 된다 ☠️☠️
이는 교모하게 enableDefaultTyping 과 GenericJackson2JsonRedisSerializer 가
맞물리면서 발생한다.
- Long 은 타입을 없이 저장한다.
- 캐싱을 읽고 역직렬화 할 때, 숫자를 보고 Jackson 이 알아서 판단한다.
- Integer 범위이면, Integer 로 타입 캐스팅
- Integer 보다 크면, Long 으로 타입 캐스팅
- Long 을 기대했는데 Integer 타입을 받아서 예외 발생
=> 굳이 Long 이여야 할 필요가 없다면, 래퍼 클래스 long 을 사용하자.
Collections.unmodifiableList
@Transactional(readOnly = true)
@Cacheable
public List<ValueDto> getList() {
return valueRepository.findAll()
.stream()
.map(ValueDto::toDto)
.toList();
}엔티티 -> DTO 변환후 캐싱할때 아무 생각없이 .toList 를 사용할 수 있다.
이 역시도 주의 해야한다! ☠️
toList 는 ImmutableCollections 타입을 기록한다.
["java.util.ImmutableCollections$ListN", [{"@class": "com.example.MyDto", ...}]]이 타입은 JDK 내부 클래스로
- 리플렉션 접근 제한
- 기본 생성자 X - Jackson 이 객체 만드는 방식 사용 불가
등을 통해 에러가 발생한다.
=> .collect(Collectors.toList()) 를 통해 ArrayList 로 생성해줘야 한다.
(Map 도 동일, Map.of 시 에러 발생)
["java.util.ArrayList", [{"@class": "com.example.MyDto", ...}]]익명, 람다 클래스
위와 같은 맥락으로 런타임 클래스명을 복원 불가능하기 때문에 역직렬화 할 수 없다.
MyService$$Lambda$123/0x00000... 추적 불가능
Deprecated
objectMapper.enableDefaultTyping(NON_FINAL, As.PROPERTY) 은
사실 2.10+ 부터 Deprecated 되어있다.
역직렬화 공격 취약점 때문인데, @class 에 악의적 클래스명을 넣으면 임의 코드 실행이 가능해진다.
var ptv = BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(Object.class)
.build();
objectMapper.activateDefaultTyping(ptv, NON_FINAL, As.PROPERTY);와 같이, TypeValidator 로 허용하는 타입을 명시하고
activeDefaultTyping 으로 설정하면 기존과 동일하게 동작한다.
당연히 Object 말고 패키지 지정이나, 객체를 허용하는게 더 좋은 방향