0. Remind
직렬화(Serialization)와 역직렬화(Deserialization)
readObject
1. 직렬화/역직렬화 과정에서의 불변식 손상 문제
- Remind에서 보았던 것 처럼
readObject
메서드는 직렬화된 객체의 byte stream을 받고, 이를 역직렬화하여 해당 정보를 가지고 있는 객체를 반환합니다.
- 책에서 보여준
Period
예제를 보도록 하겠습니다.
public final class Period {
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 + "is after then " + end);
}
}
public Date start() {
return new Date(start.getTime()); // 방어적 복사 수행
}
public Date end() {
return new Date(end.getTime());
}
@Override
public String toString() {
return start + " - " + end;
}
...
}
- 이 클래스를 직렬화 하기로 결정했다고 가정해 보면
Period
객체의 물리적 표현이 논리적 표현과 부합합니다.
- 우선 시작일과 끝일이 정해져 있고, 시작일이 끝일보다 뒤일 수 없다는 조건을 가지고 있으므로, 물리적으로도 문제가 없음을 확인할 수 있습니다.
- 따라서 기본 직렬화 형태를 사용해도 나쁘지 않습니다. 기본 직렬화 형태는 가장 단순하게 클래스에다
implements Serializable
을 추가하면 마무리될 것 처럼 보입니다.
- 문제는 이러한 방식을 사용하게 된다면
Period
객체의 불변식을 더 이상 보장할 수 없게 된다는 점입니다.
readObject의 특성
- 저번주 스터디에서 진행했던 내용 중
readObject
와 관련하여 다음과 같이 언급을 한 적이 있었습니다.
- 하지만 유효성 검증이 어려워 보안에 문제가 있으며,
보이지 않는 생성자
라고 불린다.
- 또한 책에서도
readObject
는 직렬화된 byte stream을 매개변수로 받는 생성자이다 라고 언급했습니다.
- 그 말 그대로
readObject
는 실질적으로 public
생성자와 거의 동일한 성질을 가집니다. 바로 이 이유 때문에 readObject
를 구현하거나, 사용할 때에는 일반적인 생성자를 만드는 것과 동일한 수준으로 주의를 기울여야 합니다.
- 생성자를 작성할 때 주의해야 할 것은 다음과 같습니다.
- 먼저 인수가 유효한지를 검사해야 합니다. (매개변수 유효성 검사)
- 만약 필요하다면 매개변수를 방어적으로 복사해야 합니다.
- 만약 위와 같이 주의해야 할 사항을 잘 구현하지 못하게 된다면 공격자는 아주 손쉽게 해당 클래스의 불변식을 깨뜨릴 수 있습니다.
- 만약
Period
클래스에서 기본적인 implements Serializable
만 사용했다면 공격자는 다음과 같은 코드를 통해 공격을 수행할 수 있습니다.