JAVA/Effective Java

[아이템 88] readObject 메서드는 방어적으로 작성하라

꾸준함. 2024. 5. 18. 02:38

아이템 50에서 불변인 날짜 범위 클래스를 생성할 때 가변 객체인 Date 필드를 이용했습니다.

이 때문에 불변식을 지키고 유지하기 위해 모든 생성자와 접근자에 Date 객체를 방어적으로 복사하느라 코드가 상당히 길어졌던 것을 기억할 것입니다.

 

 

 

아이템 87에서 언급했다시피 객체의 물리적 표현과 논리적 표현이 동일할 경우 기본 직렬화를 사용해도 됩니다.

따라서 위 Period 클래스가 단순히 마커 인터페이스인 Serializable를 구현하도록 수정하면 완벽한 직렬화 적용이 완료되었을 것이라고 생각이 들 것입니다.

하지만 위와 같이 적용해서는 주요한 Date 객체의 불변식을 보장하지 못합니다.

 

Period 객체가 단순히 Serializable을 구현했을 때 발생하는 문제점

  • readObject() 메서드가 실질적으로 또 다른 public 생성자이기 때문에 생성자와 똑같은 수준으로 주의를 기울여야 함
    • 따라서 readObject() 메서드에서 인수가 유효한지 검사해야 하며 필요할 경우 방어적 복사 필요
    • 아이템 50처럼 Date 객체에 방어적 복사를 적용하지 않을 경우 클래스의 불변식이 쉽게 깨질 수 있음

 

불변식을 깨드릴 용도로 스트림을 조작할 경우 문제가 생김

 

 

부연 설명

  • 위 코드의 serializedForm에서 상위 비트가 1인 바이트 값들은 byte로 형변환했는데 이는 자바가 바이트 리터럴을 지원하지 않고 byte 타입은 부호가 있는 (signed) 타입이기 때문
  • 위의 프로그램 실행 시 Sat Jan 02 05:00:00 KST 1999 - Mon Jan 02 05:00:00 KST 1984 출력
  • Period를 직렬화할 수 있도록 선언하는 것만으로도 위처럼 불변식을 깨뜨리는 객체를 생성할 수 있음
    • end가 start보다 과거 날짜

 

따라서 역직렬화 시 불변식을 만족하는지 유효성 검사 수행 필요

 

 

부연 설명

  • 조건문을 추가함에 따라 앞선 문제였던 end가 start보다 과거 날짜인 문제는 해결했지만 여전히 미묘한 문제가 내재되어 있음
    • 정상 Period 인스턴스에서 시작된 바이트 스트림 끝에 private Date 필드로의 참조를 추가할 경우 가변 Period 인스턴스를 만들 수 있는 문제점 존재
    • 공격자는 ObjectInputStream에서 Period 인스턴스를 읽은 후 스트림 끝에 추가된 `악의적인 객체 참조`를 읽어 Period 객체의 내부 정보를 얻을 수 있음
    • 이 참조로 얻은 Date 인스턴스들에 의해 Period 인스턴스는 더 이상 불변이 아니게 되는 것

 

참조로 얻은 Date 인스턴스들에 의해 Period 인스턴스가 더 이상 불변이 아닌 케이스

 

 

부연 설명

  • 이 문제의 근원은 Period 객체의 readObject() 메서드가 방어적 복사를 수행하지 않기 때문
  • 객체를 직렬화할 때는 클라이언트가 소유해서는 안되는 객체 참조를 갖는 필드를 모두 방어적으로 복사해야 함

 

readObject 메서드에서는 private 가변 요소에 대해서도 방어 복사하라


 

부연 설명

  • 방어적 복사를 유효성 검사보다 앞서 수행하며 Date의 clone() 메서드는 사용하지 않았음
    • 두 조치 모두 Period 객체를 공격으로부터 보호하는데 필요

 

  • final 필드의 경우 방어적 복사가 불가능하므로 주의해야 함
    • 따라서 readObject()를 사용하기 위해서는 start와 end 필드의 final 한정자를 제거해야 함

 

기본 readObject()를 사용해도 되는 케이스

  • transient 필드를 제외한 모든 필드의 값을 매개변수로 받아 유효성 검사 없이 필드에 대입하는 public 생성자를 추가해도 괜찮을 경우 기본 readObject() 메서드 사용해도 무방
    • 그렇지 않을 경우 앞선 예시처럼 커스텀 readObject() 메서드를 통해 유효성 검사 및 방어적 복사를 하거나 아이템 90에서 언급할 직렬화 proxy 패턴을 사용해도 됨
    • 직렬화 proxy 패턴의 경우 역직렬화를 안전하게 만드는데 필요한 노력을 경감시켜 줌

 

참고

이펙티브 자바

반응형