본문 바로가기

JAVA/Effective Java

item 88) readObject는 방어적으로 작성하라

728x90

- readObject도 바이트 스트림을 인수로 받는 일종의 public 생성자

- 다른 생성자와 똑같은 수준으로 주의를 기울여야한다.

- 인수가 유효한지 검사해야하고 필요하다면 매개변수를 방어적으로 복사해야한다.(불변성 유지)

 

 

public final class Period implements Serializable { //기본 직렬화 사용

  private Date start;
  private Date end;

  public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    if (this.start.compareTo(this.end) > 0) {
      throw new IllegalArgumentException(start + " after " + end);
    }
  }

  public Date start() {
    return new Date(start.getTime());
  }

  public Date end() {
    return new Date(end.getTime());
  }

}
public class BogusPeriod {
    
    private static final byte[] serializedForm = {
        (byte)0xac, (byte)0xed, 0x00, 0x05, 0x73, 0x72, 0x00, 0x06.... }
    
    public static void main(String[] args) {
        Period p = (Period) deserialize(serializedForm);
        System.out.println(p);
    }
    
    static Object deserialize(byte[] sf) {
        try {
            return new ObjectInputStream(new ByteArrayInputStream(sf)).readObject();
        } catch(IOException | ClassNotFoundException e) {
            throw new IllegalArgumentException(e);
        }
    }
}

- 종료시간이 시작시간보다 짧은 바이트 스트림을 입력으로 주더라도 막을 방법이 없다.

 

커스텀 직렬화를 구현하여 Period의 readObject 메서드가 defaultReadObject를 호출한 다음 객체가 유효한지 검사한다.

유효검사가 실패하면 InvalidObjectException을 던져 잘못된 역직렬화를 막는다.   

 

package com.github.sejoung.codetest.serialization;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

public class MutablePeriod {

  public Period period;

  public Date start;

  public Date end;

  public MutablePeriod() {
    try {
      ByteArrayOutputStream bos = new ByteArrayOutputStream();
      ObjectOutputStream out = new ObjectOutputStream(bos);

      // Serialize a valid Period instance
      out.writeObject(new Period(new Date(), new Date()));

      byte[] ref = {0x71, 0, 0x7e, 0, 5}; // Ref #5
      bos.write(ref); // The start field
      ref[4] = 4; // Ref # 4
      bos.write(ref); // The end field

      // Deserialize Period and "stolen" Date references
      ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
      period = (Period) in.readObject();
      start = (Date) in.readObject();
      end = (Date) in.readObject();
    } catch (Exception e) {
      throw new AssertionError(e);
    }
  }

  public static void main(String[] args) {
    MutablePeriod mp = new MutablePeriod();

    Period p = mp.period;
    Date pEnd = mp.end;

    pEnd.setTime(78);
    System.out.println(p);

    pEnd.setTime(68);
    System.out.println(p);

     //Period(start=Thu Mar 28 14:57:54 KST 2019, end=Thu Jan 01 09:00:00 KST 1970) mp 의 period를 바꿨는데 p의 end rk 가 바뀜
     //Period(start=Thu Mar 28 14:57:54 KST 2019, end=Thu Jan 01 09:00:00 KST 1970)

  }


}
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);
    }
  }

 

객체를 역직렬화 할때는 클러아인트가 소유해서는 안되는 객체 참조를 갖는 필드를 모두 반드시 방어적으로 복사해야한다.