복잡한뇌구조마냥

[JAVA] Effective Java - readObject 메서드는 방어적으로 작성하라 (88) 본문

BE/JAVA

[JAVA] Effective Java - readObject 메서드는 방어적으로 작성하라 (88)

지금해냥 2025. 9. 15. 19:14

요약약

  • readObject는 사실상 숨은 생성자다.
  • 따라서 공격자가 임의로 만든 바이트 스트림을 넣을 수 있다는 전제로 방어적으로 작성해야 한다.
  • 원칙:
    1. 방어적 복사를 가장 먼저 수행
    2. 불변식 검사 후 위배되면 InvalidObjectException 던지기
    3. 필요 시 ObjectInputValidation으로 객체 그래프 전체 유효성 검사
    4. 재정의 가능한 메서드 호출 금지 (생성자와 동일한 원칙)
  • 더 안전한 방법: 직렬화 프록시 패턴을 활용하라.

1. 왜 readObject가 위험한가?

  • 직렬화/역직렬화는 바이트 스트림으로 객체를 주고받는 메커니즘.
  • 하지만 공격자는 이 스트림을 임의로 조작해,
    • 불변식이 깨진 객체
    • 내부 상태가 노출되거나 수정 가능한 객체
      를 만들 수 있다.
  • 즉, readObject는 단순히 “저장된 객체 복원”이 아니라 보안 구멍이 될 수 있다.

2. 방어적으로 작성하는 방법

① 방어적 복사

  • 역직렬화 과정에서 가변 객체 참조를 그대로 저장하면 위험.
  • 반드시 방어적 복사를 수행해야 한다.
private Date start;
private Date end;

private void readObject(ObjectInputStream in) 
    throws IOException, ClassNotFoundException {
    in.defaultReadObject();

    // 방어적 복사 (원본 참조를 그대로 두지 않는다)
    start = new Date(start.getTime());
    end = new Date(end.getTime());

    if (start.after(end)) {
        throw new InvalidObjectException("시작 시간이 종료 시간보다 늦습니다.");
    }
}

✅ 방어적 복사는 유효성 검사보다 먼저 수행해야 한다.
(안 그러면 공격자가 참조를 바꿔치기해 검사 후 다시 값을 조작할 수 있음)


② 불변식 검사

  • 모든 불변식을 확인하고, 깨져 있다면 InvalidObjectException 던지기.
  • 생성자에서 하는 불변식 검증과 동일하게 생각해야 한다.

③ 객체 그래프 전체 검사

  • 복잡한 객체 그래프라면 개별 클래스 단위 검사만으로 부족할 수 있다.
  • 이 경우 ObjectInputValidation 인터페이스를 활용해 역직렬화가 끝난 후 전체 유효성 검사를 수행할 수 있다.

④ 재정의 가능한 메서드 호출 금지

  • 생성자와 마찬가지로 readObject 내부에서도 재정의 가능한 메서드 호출 금지.
  • 역직렬화 과정에서 하위 클래스 상태가 완전히 복원되기 전, 오버라이드된 메서드가 실행되어 예기치 않은 동작을 일으킬 수 있다.

3. 직렬화 프록시 패턴 (Serialization Proxy Pattern)

  • 가장 안전한 방법: 직렬화 프록시 패턴 사용.
  • 프록시 객체를 통해 직렬화/역직렬화 과정을 제어하면,
    • 불변식 위반 방지
    • 보안성 강화
    • readObject의 방어 코드 작성 부담 감소
  • 단점: 약간의 성능 비용이 있지만, 안전성과 유지보수성 측면에서 권장된다.

4. 지켜야 할 규칙 요약

  1. 방어적 복사: private 참조 필드는 역직렬화 시 반드시 복사
  2. 불변식 검사: 깨져 있으면 InvalidObjectException
  3. 객체 그래프 검사: 필요하면 ObjectInputValidation 활용
  4. 재정의 가능한 메서드 호출 금지
  5. 직렬화 프록시 패턴 고려

✅ 결론

readObject는 단순한 역직렬화 메서드가 아니라 숨은 생성자다.
따라서 생성자처럼 방어적으로 작성해야 하며, 절대 바이트 스트림을 신뢰해서는 안 된다.
방어적 복사 → 불변식 검사 → 필요 시 전체 유효성 검사 순서로 작성하고,
안전성을 더 높이려면 직렬화 프록시 패턴을 활용하라.

LIST