Serializable의 보안 이슈
Serializable을 구현하기로 결정한 순간 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스를 생성할 수 있게 된다. 따라서 버그와 보안 문제가 일어날 가능성이 커진다는 문제가 있다. 이를 해결하기 위해 직렬화 프록시 패턴을 사용할 수 있다.
직렬화 프록시 패턴
직렬화 프록시 패턴을 구현하는 방법은 다음과 같다.
- 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계해 private static으로 선언한다.
- 이 중첩 클래스가 바로 바깥 클래스의 직렬화 프록시이다.
- 중첩 클래스의 생성자는 단 하나여야 하며, 바깥 클래스를 매개변수로 받아야 한다.
- 이 생성자는 단순히 인수로 넘어온 인스턴스의 데이터를 복사한다.
private static class SerializationProxy implements Serializable {
private final Date start;
private final Date end;
SerializationProxy(Period p) {
this.start = p.start;
this.end = p.end;
}
private static final long serialVersionUID = 234098243823485285L; // Any number will do (Item 87)
}
- 바깥 클래스에 다음의 writeReplace 메서드를 추가한다.
- 이 클래스는 범용적이니 직렬화 프록시를 사용하는 모든 클래스에 그대로 복사해 쓰면 된다.
- 이 메서드 자바의 직렬화 시스템이 바깥 클래스의 인스턴스 대신 SerializationProxy의 인스턴스를 반환하게 하는 역할을 한다.
private Object writeReplace() {
return new SerializationProxy(this);
}
- 불변식을 훼손하려는 공격은 readObject 메서드를 바깥 클래스에 추가하여 막아낼 수 있다.
public final class Period implements Serializable {
private final Date start;
private final 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);
}
...
private static class SerializationProxy implements Serializable { }
private Object writeReplace() {}
// readObject method for the serialization proxy pattern
private void readObject(ObjectInputStream stream)
throws InvalidObjectException {
throw new InvalidObjectException("Proxy required");
}
}
- 바깥 클래스와 논리적으로 동일한 인스턴스를 반환하는 readResolve 메서드를 SerializationProxy에 추가한다.
- 역직렬화 시에 직렬화 시스템이 직렬화 프록시를 다시 바깥 클래스의 인스턴스로 변환하게 해준다.
private Object readResolve() {
return new Period(start, end);
}
이 직렬화 프록시 패턴은 생성자 없이 인스턴스를 생성할 수 있게 만드는 직렬화의 단점을 상당 부분 제거한다. 따라서 역직렬화된 인스턴스가 해당 클래스의 불변식을 만족하는지 검사할 또 다른 수단을 강구하지 않아도 된다. 또 강력한 강점 중 하나로 직렬화 프록시 패턴은 역직렬화한 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 작동한다.
직렬화 프록시 패턴의 한계
- 클라이언트가 멋대로 확장할 수 있는 클래스에는 적용할 수 없다.
- 객체 그래프에 순환이 있는 클래스에도 적용할 수 없다.
- 속도가 느려진다.