티스토리 뷰
728x90
반응형
적시에 방어적 복사본을 만들라
- 자바는 안전한 언어입니다. 네이티브 메서드를 사용하지 않으니 C, C++ 같이 안전하지 않은 언어에서 흔히보는 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류로부터 안전합니다. 자바로 작성한 클래스는 시스템의 다른 부분에서 무슨 짓을 하든 그 불변식이 지켜집니다. 메모리 전체를 하나의 거대한 배열로 다루는 언어안에서는 누릴 수 없는 강점입니다.
💡 문제가 발생하는 상황
- 아래 Period 객체는 생성자에서 유효성 검사를 하고 있습니다. 하지만 main 메서드에서 객체 생성 후에 값을 수정하고 있어 문제가 발생할 수 있습니다.
- 자바 8 이후로는 Date 클래스 대신 불변인 Instant를 사용하거나 LocalDateTime 또는 ZonedDateTime을 사용해도 됩니다.
- Date는 낡은 API이니 새로운 코드를 작성할 때는 더이상 사용해서는 안됩니다.
- 외부로부터 공격받은 Period 인스턴스의 내부를 보호하려면 생성자에서 받은 가변 매개변수 각각을 방어적 복사해야합니다. 그 다음 Period 인스턴스 내부에서는 원본이 아닌 복사본을 사용합니다.
public final class Period {
private final Date start;
private final Date end;
public Period(Date start, Date end) {
if (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + "가 " + end + "보다 늦습니다.");
}
this.start = start;
this.end = end;
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
public class Example {
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
start.setYear(78); // period의 내부 수정이 가능합니다.
}
}
💡 문제 해결 방법
- 매개변수의 유효성을 검사하기 전 방어적 복사본을 만들고 해당 복사본을 이용하여 유효성을 검사합니다.
- 순서가 부자연스러워 보일 수 있으나, 반드시 이렇게 작성해야 합니다.
- 멀티 스레딩 환경이라면 원본 객체의 유효성을 검사한 후 복사본을 만드는 그 찰나의 취약한 순간에 다른 스레드가 원본 객체를 수정할 위험이 있습니다.
- 방어적 복사를 매개변수 유효성 검사 전에 수행하면 이런 위험에서 해방될 수 있습니다. 이를 컴퓨터 보안 커뮤니티에서는 검사시점/사용시점 공격 혹은 TOCTOU 공격이라 합니다.
- Date는 final이 아니므로 clone이 Date가 정의한게 아닐 수도 있습니다. 즉 clone이 악의를 가진 하위 클래스의 인스턴스를 반환할 수도 있습니다. 그렇기 때문에 Date의 clone 메서드를 사용하지 않았습니다. 또한 매개변수가 제3자에 의해 확장될 수 있는 타입이라면 방어적 복사본을 만들 때 clone을 사용해서는 안됩니다.
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 (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + "가 " + end + "보다 늦습니다.");
}
}
public Date getStart() {
return start;
}
public Date getEnd() {
return end;
}
}
💡 Period 인스턴스의 메서드를 통한 공격
public class Example {
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period period = new Period(start, end);
period.getEnd().setYear(78); // Period 인스턴스의 메서드를 통한 공격
}
}
💡 문제 해결 방법
- 생성자에서는 방어적 복사본을 통해 공격으로부터 막았지만 getter와 같은 접근자 메서드를 통해 필드를 수정할려는 경우도 마찬가지로 방어적 복사본을 통해 해결할 수 있습니다.
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 (start.compareTo(end) > 0) {
throw new IllegalArgumentException(start + "가 " + end + "보다 늦습니다.");
}
}
public Date getStart() {
return new Date(start.getTime());
}
public Date getEnd() {
return new Date(end.getTime());
}
}
💡 너무 많은 방어적 복사..?
- 객체를 생성할 때, 접근자를 통해 필드에 접근할려고 할 때마다 매번 새로운 객체를 생성하는 방어적 복사를 하게된다면 성능저하가 필수적으로 뒤따를 수 밖에 없습니다. 그렇기에 되도록이면 불변 객체를 조합해 객체를 구성함으로써 방어적 복사를 할 일 자체를 막아야 합니다.
💡 방어적 복사 생략
- 방어적 복사의 목적은 외부에서 내부의 데이터를 함부로 변경할 수 없도록 막음으로써 통제권을 유지하는데 있습니다. 하지만 클라이언트에서 매개변수로 전달하는 인수들에 대한 통제권을 명백히 이전하고 수정하는 일이 없을 경우 방어적 복사본을 생략할 수 있습니다. 다만 이 경우에는 클라이언트가 통제권을 넘겨주는 가변 객체에 대한 내용을 통제권을 넘겨받는 메서드와 생성자에 문서화를 해야합니다.
- 이렇게 통제권을 넘겨받기로 한 메서드, 생성자를 가진 클래스들은 취약할 수 밖에 없는데 따라서 해당 클래스와 클라이언트가 상호 신뢰할 수 있을 때, 혹은 불변식이 깨지더라도 그 영향이 오직 호출한 클라이언트에 국한될때 방어적 복사를 생략해도 됩니다.
✔️ 정리
- 클래스가 클라이언트로부터 받은 혹은 클라이언트로 반환하는 구성 요소가 가변이라면 그 요소는 반드시 방어적 복사를 해야합니다.
- 복사 비용이 너무 크거나 클라이언트가 그 요소를 잘못 수정할 일이 없음을 신뢰할 수 있다면 방어적 복사를 생략해도 되며 대신 해당 구성 요소가 수정되었을 경우 그 책임은 클라이언트에 있음을 문서화해야 합니다.
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item52. 다중정의는 신중하게 사용하라 (0) | 2022.08.05 |
---|---|
이펙티브 자바 - Item51. 메서드 시그니처를 신중히 설계하라 (0) | 2022.08.04 |
이펙티브 자바 - Item49. 매개변수가 유효한지 검사하라 (0) | 2022.08.02 |
이펙티브 자바 - Item48. 스트림 병렬화는 주의해서 적용하라 (0) | 2022.08.01 |
이펙티브 자바 - Item47. 반환 타입으로는 스트림보다 컬렉션이 낫다 (0) | 2022.08.01 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- 공간 기반 아키텍처
- @ControllerAdvice
- redis 대기열 구현
- microkernel architecture
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- spring boot redisson sorted set
- spring boot 엑셀 다운로드
- spring boot redis 대기열 구현
- 자바 백엔드 개발자 추천 도서
- pipe and filter architecture
- 서비스 기반 아키텍처
- redis sorted set으로 대기열 구현
- transactional outbox pattern
- spring boot poi excel download
- spring boot excel download paging
- 람다 표현식
- spring boot redisson destributed lock
- spring boot excel download oom
- spring boot redisson 분산락 구현
- service based architecture
- polling publisher spring boot
- java userThread와 DaemonThread
- JDK Dynamic Proxy와 CGLIB의 차이
- 트랜잭셔널 아웃박스 패턴 스프링부트
- pipeline architecture
- 레이어드 아키텍처란
- space based architecture
- transactional outbox pattern spring boot
- redis sorted set
- java ThreadLocal
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
글 보관함