Title: 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 · Issue #206 · Study-2-Effective-Java/Effective-Java · GitHub
Open Graph Title: 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 · Issue #206 · Study-2-Effective-Java/Effective-Java
X Title: 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 · Issue #206 · Study-2-Effective-Java/Effective-Java
Description: Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스를 생성할 수 있음 버그와 보안 문제...
Open Graph Description: Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니...
X Description: Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니...
Opengraph URL: https://github.com/Study-2-Effective-Java/Effective-Java/issues/206
X: @github
Domain: patch-diff.githubusercontent.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라","articleBody":"### Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203\r\n\r\n\u003cdiv type='discussions-op-text'\u003e\r\n\r\n\u003csup\u003eOriginally posted by **JoisFe** April 9, 2023\u003c/sup\u003e\r\n# 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라\r\n## 문제점\r\n- Serializable을 구현하기로 결정한 순간 언어의 정상 메커니즘인 생성자 이외의 방법으로 인스턴스를 생성할 수 있음\r\n### 버그와 보안 문제가 일어날 가능성이 커짐\r\n\r\n## 해결책\r\n### 직렬화 프록시 패턴 (serialization proxy pattern)\r\n- 먼저 바깥 클래스의 논리적 상태를 정밀하게 표현하는 중첩 클래스를 설계해 private static으로 선언\r\n- 해당 중첩 클래스가 바로 바깥 클래스의 직렬화 프록시\r\n- 중첩 클래스의 생성자는 단 하나여야 하며 바깥 클래스를 매개변수로 받아야 한다.\r\n- 이 생성자는 단순히 인수로 넘어온 인스턴스의 데이터를 복사\r\n- 일관성 검사나 방어적 복사도 필요 없음!\r\n- 설계상 직렬화 프록시의 기본 직렬화 형태는 바깥 클래스의 직렬화 형태로 쓰기에 이상적\r\n- 그리고 바깥 클래스와 직렬화 프록시 모두 Serializable을 구현한다고 선언해야 함\r\n\r\n``` java\r\npublic final class Period {\r\n\r\n private final Date start;\r\n private final Date end;\r\n\r\n Period(Date start, Date end) {\r\n if (start.compareTo(end) \u003e 0) {\r\n throw new IllegalArgumentException(start + \"가 \" + end + \"보다 늦다.\");\r\n }\r\n \r\n this.start = start;\r\n this.end = end;\r\n }\r\n \r\n public Date start() {\r\n return this.start;\r\n }\r\n \r\n public Date end() {\r\n return this.end;\r\n }\r\n\r\n private static class SerializationProxy implements Serializable {\r\n private final Date start;\r\n private final Date end;\r\n\r\n public SerializationProxy(Period p) {\r\n this.start = p.start;\r\n this.end = p.end;\r\n }\r\n\r\n // Period.SerializationProxy 용 readResolve 메서드\r\n private Object readResolve() {\r\n return new Period(this.start, this.end);\r\n }\r\n }\r\n \r\n private static final long serialVersionUID = 453452354;\r\n \r\n private static final long serialVersionUID = 453452354;\r\n\r\n // 직렬화 프록시 패턴용 writeReplace 메서드\r\n private Object writeReplace() {\r\n return new SerializationProxy(this);\r\n }\r\n\r\n // 직렬화 프록시 패턴용 readObject 메서드\r\n private Object readObject(ObjectInputStream stream) throws InvalidObjectException {\r\n throw new InvalidObjectException(\"프록시가 필요합니다.\");\r\n }\r\n}\r\n```\r\n\r\n- SerializationProxy 클래스는 Period 클래스의 직렬화 프록시이다.\r\n\r\n``` java\r\n // 직렬화 프록시 패턴용 writeReplace 메서드\r\n private Object writeReplace() {\r\n return new SerializationProxy(this);\r\n }\r\n```\r\n- 바깥 클래스에 다음의 writeReplace 메서드를 추가\r\n- 해당 메서드는 범용적이므로 직렬화 프록시를 사용하는 모든 클래스에 그대로 복사해 쓰면 됨\r\n- 이 메서드는 자바의 직렬화 시스템이 바깥 클래스의 인스턴스 대신 SerializationProxy 인스턴스를 반환하게 하는 역할을 함\r\n- 달리 말하면 직렬화가 이뤄지기 전에 바깥 클래스의 인스턴스를 직렬화 프록시로 변환해줌\r\n- writeReplace 덕분에 직렬화 시스템은 결코 바깥 클래스의 직렬화된 인스턴스를 생성해낼 수 없음\r\n- 하지만 공격자는 불변식을 훼손하고자 이런 시도를 해볼 수 있음\r\n\r\n``` java\r\n // 직렬화 프록시 패턴용 readObject 메서드\r\n private Object readObject(ObjectInputStream stream) throws InvalidObjectException {\r\n throw new InvalidObjectException(\"프록시가 필요합니다.\");\r\n }\r\n```\r\n\r\n- readObject 메서드를 바깥 클래스에 추가하면 이 공격을 가볍게 막아낼 수있음\r\n\r\n``` java\r\n // Period.SerializationProxy 용 readResolve 메서드\r\n private Object readResolve() {\r\n return new Period(this.start, this.end);\r\n }\r\n```\r\n\r\n- 바깥 클래스와 논리적으로 동일한 인스턴스를 반환하는 readResolve 메서드를 SerializationProxy 클래스에 추가\r\n- 이 메서드는 역직렬화 시에 직렬화 시스템이 직렬화 프록시를 다시 바깥 클래스의 인스턴스로 변환하게 해줌\r\n- readResolve 메서드는 공개된 API 만을 사용해 바깥 클래스의 인스턴스를 생성하는데 이 패턴이 아름다운 이유가 여기 있음\r\n- 직렬화는 생성자를 이용하지 않고도 인스턴스를 생성하는 기능을 제공하는 이 패턴은 직렬화의 이런 언어도단적 특성을 상당 부분 제거\r\n- 즉 일반 인스턴스를 만들 때와 똑같은 생성자, 정적 팩터리 혹은 다른 메서드를 사용해 역질렬화된 인스턴스를 생성하는 것\r\n- 따라서 역직렬화된 인스턴스가 해당 클래스의 불변식을 만족하는지 검사할 또 다른 수단을 강구하지 않아도 됨\r\n- 그 클래스의 정적 팩터리나 생성자가 불변식을 확인해주고 인스턴스 메서드들이 불변식을 잘 지켜준다면 따로 더 해줘야 할 일이 없음\r\n\r\n## 프록시 패턴 vs 방어적 복사\r\n- 방어적 복사처럼 직렬화 프록시 패턴은 가짜 바이트 스트림 공격과 내부 필드 탈취 공격을 프록시 수준에서 차단해줌\r\n- 방어적 복사 방식과 달리 직렬화 프록시는 Period의 필드를 final로 선언해도 되므로 Period 클래스를 진정한 불변으로 만들 수 있음\r\n- 또한 이리저리 고민할 거리도 없어짐\r\n- 어떤 필드가 기만적인 직렬화 공격의 목표가 될지 고민하지 않아도 되며 역직렬화 때 유효성 검사를 수행하지 않아도 됨\r\n\r\n### 직렬화 프록시 패턴이 readObject 에서의 방어적 복사보다 강력한 경우\r\n- 직렬화 프록시 패턴은 역직렬화환 인스턴스와 원래의 직렬화된 인스턴스의 클래스가 달라도 정상 동작\r\n- 실전에서 크게 쓸모가 없어 보이나 쓸모가 있음\r\n\r\n### EX) EnumSet (#91)\r\n- 해당 클래스는 public 생성자 없이 정적 팩터리들만 제공\r\n- 클라이언트 입장에서 이 팩터리들이 EnumSet 인스턴스를 반환하는 걸로 보이지만 현재의 OpenJDK를 보면 열거 타입의 크기에 따라 두 하위 클래스 중 하나의 인스턴스를 반환\r\n- 열거 타입의 원소가 64개 이하이면 RegularEnumSet을 사용하고 그보다 크면 JumboEnumSet을 사용\r\n\r\n### EnumSet 직렬화 프록시 패턴\r\n- 원소 64개 짜리 열거 타입을 가진 EnumSet을 직렬화 한 다음 원소 5개를 추가하고 역직렬화하면 어떤 일이 벌어질지 알아보자\r\n- 처음 직렬화된 것은 RegularEnumSet 인스턴스\r\n- 하지만 역직렬화는 JumboEnumSet 인스턴스로 하면 좋을 것\r\n- 그리고 EnumSet은 직렬화 프록시 패턴을 사용해서 실제로도 아래와 같이 동작\r\n\r\n``` java\r\nprivate static class SerializationProxy \u003cE extends Enum\u003cE\u003e\u003e implements Serializable {\r\n\r\n // 이 EnumSet의 원소 타입\r\n private final Class\u003cE\u003e elementType;\r\n\r\n // 이 EnumSet 안의 원소들\r\n private final Enum\u003c?\u003e[] elements;\r\n\r\n SerializationProxy(Enum\u003cE\u003e set) {\r\n this.elementType = set.elementType;\r\n this.elements = set.toArray(new Enum\u003c?\u003e[0]);\r\n }\r\n\r\n private Object readResolve() {\r\n EnumSet\u003cE\u003e result = EnumSet.noneOf(this.elementType);\r\n\r\n for (Enum\u003c?\u003e e : this.elements) {\r\n result.add((E) e);\r\n }\r\n\r\n return result;\r\n }\r\n\r\n private static final long serialVersionUID = 23542435L;\r\n }\r\n```\r\n\r\n## 직렬화 프록시 패턴의 한계\r\n### 1. 클라이언트가 멋대로 확장할 수 있는 클래스에는 적용할 수 없음\r\n### 2. 객체 그래프에 순환이 있는 클래스에도 적용할 수 없음\r\n- 이러한 객체의 메서드를 직렬화 프록시의 readResolve 안에서 호출하려 하면 ClassCastException 발생\r\n- 직렬화 프록시만 가졌을 뿐 실제 객체는 아직 만들어진 것이 아니기 때문\r\n\r\n## 직렬화 프록시 패턴이 주는 대가\r\n### 직렬화 프록시 패턴은 강력함과 안정성을 주지만 그만한 대가가 따름\r\n- 위 Period 코드가 방어적 복사에 비해 14% 정도 느려짐\r\n\r\n## 정리\r\n### 제 3자가 확장할 수 없는 클래스라면 가능한 한 직렬화 프록시 패턴을 사용하자!\r\n- 중요한 불변식을 안정적으로 직렬화해주는 가장 쉬운 방법 중 하나일 것","author":{"url":"https://github.com/JoisFe","@type":"Person","name":"JoisFe"},"datePublished":"2023-04-10T14:38:47.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/206/Effective-Java/issues/206"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:76c9c892-2da6-462c-7a4f-5f6697c87295 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | D604:3F983D:8EE5EFF:BD53CA1:696DFF67 |
| html-safe-nonce | be0bb8efb952c7555aaac0fbe2192dc25f22d1a1797ac32e47586ee4c0156600 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJENjA0OjNGOTgzRDo4RUU1RUZGOkJENTNDQTE6Njk2REZGNjciLCJ2aXNpdG9yX2lkIjoiMTc4ODI4MjI2NzY1MDM1OTE0MyIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | bac8f2b6fec8629b9ecec85ae00c4093375bf6ea667bd6e867644a84b402899c |
| hovercard-subject-tag | issue:1660884578 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/Study-2-Effective-Java/Effective-Java/206/issue_layout |
| twitter:image | https://opengraph.githubassets.com/a0083439b1ad90cbec313ebe9cfba53629980bb5c2e887d642d37a9d7a764d02/Study-2-Effective-Java/Effective-Java/issues/206 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/a0083439b1ad90cbec313ebe9cfba53629980bb5c2e887d642d37a9d7a764d02/Study-2-Effective-Java/Effective-Java/issues/206 |
| og:image:alt | Discussed in https://github.com/orgs/Study-2-Effective-Java/discussions/203 Originally posted by JoisFe April 9, 2023 아이템 90. 직렬화된 인스턴스 대신 직렬화 프록시 사용을 검토하라 문제점 Serializable을 구현하기로 결정한 순간 언어의 정상 메커니... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | JoisFe |
| hostname | github.com |
| expected-hostname | github.com |
| None | 4922b452d03cd8dbce479d866a11bc25b59ef6ee2da23aa9b0ddefa6bd4d0064 |
| turbo-cache-control | no-preview |
| go-import | github.com/Study-2-Effective-Java/Effective-Java git https://github.com/Study-2-Effective-Java/Effective-Java.git |
| octolytics-dimension-user_id | 120388640 |
| octolytics-dimension-user_login | Study-2-Effective-Java |
| octolytics-dimension-repository_id | 577325341 |
| octolytics-dimension-repository_nwo | Study-2-Effective-Java/Effective-Java |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 577325341 |
| octolytics-dimension-repository_network_root_nwo | Study-2-Effective-Java/Effective-Java |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7e5ae23c70136152637ceee8d6faceb35596ec46 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width