Skip to content

Latest commit

 

History

History
68 lines (49 loc) · 3.31 KB

88_readObject메서드_방어적으로_작성하라.md

File metadata and controls

68 lines (49 loc) · 3.31 KB

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

readObject 메서드

  • readObject는 바이트 스트림을 받는 생성자라고 할 수 있다.
    • readObject 메서드는 실질적으로 또 다른 생성자이기 때문에 같은 수준으로 불변식을 보장하기 위해서 주의를 기울여야한다.
    • 생성자처럼 인수가 유효한지 검사하고, 필요하면 매개변수를 방어적으로 복사해야한다.

유효성 검사

private void readObject(ObjectInputStream s)
        throws IOException, ClassNotFoundException {

    // 불변식을 만족하는지 검사한다.
    if (start.compareTo(end) > 0) {
        throw new InvalidObjectException(start + "after" + end);
    }
}

방어적 복사

  • 정상적인 Period 인스턴스에서 시작된 바이트 스트림 끝에 private Date 필드로의 참조를 추가하면 가변적인 Period 인스턴스를 만들어낼 수 있다.
  • 공격자가 역직렬화를 통해 바이트 스트림 끝에 추가된 악의적인 객체 참조를 읽으면 Period의 내부 정보를 얻을 수 있다.
  • 이 참조를 이용하여 인스턴스를 수정할 수 있다. 즉, 불변이 아니게 되는 것이다.
  • 역직렬화를 할 때는 클라이언트가 접근해서는 안 되는 객체 참조를 갖는 필드는 모두 방어적으로 복사를 해야 한다.
// 방어적 복사와 유효성 검사를 모두 수행한다.
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
    s.defaultReadObject();

    // 가변 요소들을 방어적으로 복사한다.
    start = new Date(start.getTime());
    end = new Date(end.getTime());

    // 불변식을 만족하는지 검사한다.
    if (start.compareto(end) > 0) {
        throw new InvalidObjectException(start + " after " + end);
    }
}
  • 한편 final 필드는 방어적 복사가 불가능하다. 그래서 readObject 메서드를 사용하려면 final 제거하자. 공격 위험에 노출되는것 보다 낫다.

그럼 언제 기본 readObject를 사용할까?

  • transient 필드를 제외한 모든 필드의 값을 매개변수로 받아 유효성 검사없이 필드에 대입하는 public 생성자를 추가하면 안된다고 판단되면?
    • 안된다면 커스텀 readObject메서드를 만들어 모든 유효성 검사와 방어적 복사를 수행해야한다.
    • 가장 추천하는건 직렬화 프록시 패턴을 사용하는 방법이다. (안전하게 만드는데 필요한 노력을 줄여줌)
  • final이 아닌 직렬화 가능한 클래스라면 생성자처럼 readObject 메서드도 재정의(overriding) 가능한 메서드를 호출해서는 안 된다. (아이템 19)
    • 하위 클래스의 상태가 완전히 역직렬화 되기 전에 하위 클래스에서 재정의된 메서드가 실행되기 때문이다.

안전한 readObject 메서드 작성 지침

  • private 이여야 하는 객체 참조 필드는 각 필드가 가리키는 객체를 방어적으로 복사하라.
  • 모든 불변식을 검사하고, 어긋난다면 InvalidObjectException을 던져라.
  • 역직렬화 후 객체 그래프 전체의 유효성을 검사해야 한다면 ObjectInputValidation를 사용하라.
  • 재정의(Overriding) 가능한 메서드는 호출하지 말자.