티스토리 뷰
728x90
반응형
직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라
- Serializable 인터페이스를 구현하는 순간 생성자와는 별개로 readObject 메서드로 인해 바이트 스트림을 매개변수로 받는 또 하나의 생성자가 생긴다고 하였습니다. 이러한 방법으로 인해 버그와 보안 문제가 발생할 수 있습니다. 하지만 직렬화 프록시 패턴을 사용하여 어느정도 위험을 해소할 수 있습니다.
💡 직렬화 프록시 패턴
- 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계해 private static 으로 선언하는 중접 클래스를 만드는데 이 클래스가 바깥 클래스의 직렬화 프록시를 수행합니다.
특징
- 중첩 클래스의 생성자는 단 하나여야 합니다.
- 생성자는 바깥 클래스를 매개변수로 받아야 합니다.
- 생성자의 역할은 단순히 인수로 넘어온 인스턴스의 데이터를 복사합니다.
- 바깥 클래스, 직렬화 프록시 클래스 모두 Serializable 인터페이스를 구현해야 합니다.
💡 예제 코드
public final class Period implements Serializable {
private static final long serialVersionUID = 1L;
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + "가" + end + "보다 늦습니다.");
}
this.start = start;
this.end = end;
}
// 직렬화 프록시 패턴용 writeReplace 메서드
private Object writeReplace() {
return new SerializationProxy(this);
}
// 직렬화 프록시 패턴용 readObject 메서드
private void readObject(ObjectInputStream s) throws IOException {
throw new InvalidObjectException("Proxy required !");
}
// 직렬화 프록시 클래스
private static class SerializationProxy implements Serializable {
private static final long serialVersionUID = 1L;
private final Date start;
private final Date end;
public SerializationProxy(Period period) {
this.start = period.start;
this.end = period.end;
}
private Object readResolve() {
return new Period(start, end);
}
}
}
💡 writeReplace 메서드
- 해당 메서드의 역할은 자바 직렬화 시스템이 바깥 클래스의 인스턴스 대신 SerializationProxy의 인스턴스를 반환하게 하는 역할을 수행합니다. 직렬화가 이루어지기 전에 바깥 클래스의 인스턴스를 직렬화 프록시 객체로 변경시켜 줍니다. 이 메서드로 인해 자바 직렬화 시스템은 바깥 클래스의 인스턴스를 생성할 수 없습니다. 하지만 공격자는 불변식을 훼손하고자 readObject 메서드를 사용하여 공격할 수 있습니다.
private Object writeReplace() {
return new SerializationProxy(this);
}
💡 readObject 메서드
- 공격자는 불변식을 훼손하고자 readObject 메서드로 공격을 가할 수 있지만 아래와 같이 작성하면 공격을 막을 수 있습니다.
- readObject, writeObject 메서드가 있다면 기본적으로 Serialization 과정에서 ObjectInputStream, ObjetOutputStream이 호출하게 되는데 그 안에 커스텀 로직을 넣어 수행해도 됩니다.
private void readObject(ObjectInputStream s) throws IOException {
// readObject는 역직렬화할 때(직렬화된 데이터를 다시 객체로 만들때)
// 아래처럼 하게되면 Period 객체로 역직렬화할 때 예외 발생시킴
throw new InvalidObjectException("Proxy 필요!");
}
💡 readResolve 메서드
- 바깥 클래스(Period)와 동일한 인스턴스를 반환하는 readResolve 메서드를 SerializationProxy 클래스에 추가하면 직렬화는 생성자를 이용하지 않고 인스턴스를 생성하는 기능을 제공하는데, 이 메서드를 사용하면 일반 인스턴스를 만들 때와 똑같은 생성자, 정적 팩토리, 혹은 다른 메서드를 사용해 역직렬화된 인스턴스를 생성하는 것입니다. 따라서 역직렬화된 인스턴스가 해당 클래스의 불변식을 만족하는지 검사할 또 다른 수단을 강구하지 않아도 됩니다.
private Object readResolve() {
return new Period(start, end);
}
💡 직렬화 프록시 패턴의 장점
- 방어적 복사처럼 직렬화 프록시 패턴은 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 프록시 수준에서 차단해줍니다.
- 직렬화 프록시는 Period 필드를 final로 선언해도 무방하므로 Period 클래스는 불변식을 지킬 수 있습니다.
- 어떤 필드가 기만적인 직렬화 공격의 목표가 될 지 고민하지 않아도 되며, 역직렬화할 때 유효성 검사를 수행하지 않아도 됩니다.
💡 직렬화 프록시 패턴의 한계
- 클라이언트가 멋대로 확장할 수 있는 클래스에는 적용할 수 없습니다.
- 객체 그래프에 순환이 있는 클래스에는 적용할 수 없습니다. 이런 객체의 메서드를 직렬화 프록시의 readResolve 안에서 호출하게 된다면 ClassCastException이 발생합니다. 그 이유눈 직렬화 프록시는 프록시일뿐 실제 객체는 아직 만들어지지 않았기 때문입니다.
- 직렬화 프록시 패턴을 사용하게 되면 방어적 복사할때보다 성능이 느려집니다.
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item88. readObject 메서드는 방어적으로 작성하라 (0) | 2022.08.27 |
---|---|
이펙티브 자바 - Item86. Serialization을 구현할지는 신중히 결정하라 (0) | 2022.08.27 |
이펙티브 자바 - Item85. 자바 직렬화의 대안을 찾으라 (1) | 2022.08.27 |
이펙티브 자바 - Item83. 지연 초기화는 신중히 사용하라 (0) | 2022.08.25 |
이펙티브 자바 - Item82. 스레드 안전성 수준을 문서화하라 (0) | 2022.08.24 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- redis 대기열 구현
- spring boot excel download paging
- JDK Dynamic Proxy와 CGLIB의 차이
- 레이어드 아키텍처란
- pipeline architecture
- @ControllerAdvice
- spring boot redisson 분산락 구현
- 서비스 기반 아키텍처
- spring boot redisson sorted set
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- spring boot redisson destributed lock
- service based architecture
- transactional outbox pattern spring boot
- java ThreadLocal
- microkernel architecture
- polling publisher spring boot
- spring boot excel download oom
- redis sorted set으로 대기열 구현
- 공간 기반 아키텍처
- pipe and filter architecture
- space based architecture
- spring boot 엑셀 다운로드
- 자바 백엔드 개발자 추천 도서
- redis sorted set
- spring boot poi excel download
- java userThread와 DaemonThread
- transactional outbox pattern
- 트랜잭셔널 아웃박스 패턴 스프링부트
- 람다 표현식
- spring boot redis 대기열 구현
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함