JCF 의 HashMap 코드를 보다가 SerialVersionUID 를 보고 호기심에 학습한 내용

 

 

 

1. 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제

  1. 클래스의 구조가 변경되었을 때 오류가 발생한다.
    • 예시: 멤버 변수 타입 변경, 멤버 변수 삭제
  2. 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 SerialVersionUID가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.

 

2. SerialVersionUID??

  1. SUID 는 직렬화와 역직렬화 과정에서 값이 서로 맞는지 확인한 후에 처리한다.(맞지 않다면 InvalidClassException 를 반환한다.)
  2. 자바의 직렬화 스펙 정의를 살펴보면 SUID 값은 필수가 아니며 선언되어 있지 않으면 클래스의 기본 해시값을 사용한다. (이 값들은 클래스의 이름, 생성자 등과 같이 클래스의 구조를 이용해서 생성)

 

필드 추가 후 역직렬화가 불가능함을 확인하는 테스트 코드

class DefaultSerialEntity implements Serializable {

    private String title;
    private String pressName;
    private String reporterName;

    // 새로운 필드 추가
    private String phoneNumber;

    public DefaultSerialEntity(String title, String pressName, String reporterName) {
        this.title = title;
        this.pressName = pressName;
        this.reporterName = reporterName;
    }

    @Override
    public String toString() {
        return "Article{" +
                "title='" + title + '\'' +
                ", pressName='" + pressName + '\'' +
                ", reporterName='" + reporterName + '\'' +
                '}';
    }
}

// test code
class DefaultSerialEntityTest {
    
    @Test
    @DisplayName("DefaultSerialEntity 에 phoneNumber 필드를 추가할 경우 다른 역직렬화 값을 반환하여 예외를 반환한다")
    void defaultSerializeWhenAddField() throws IOException, ClassNotFoundException {
        //given, when
        // phoneNumber 필드 추가 이전 직렬화 값
        String serializedByteStrBeforeFieldAdd = "rO0ABXNyACtjb20uY29vcGVyLnNlcmlhbGl6YWJsZS5EZWZhdWx0U2VyaWFsRW50aX" +
                "R54Lm3/1et2ggCAANMAAlwcmVzc05hbWV0ABJMamF2YS9sYW5nL1N0cmluZztMAAxyZXBvcnRlck5hbWVxAH4AAUwABXRpdGxlc" +
                "QB+AAF4cHQAC3ByZXNzTmFtZTExdAAOcmVwb3J0ZXJOYW1lMTF0AAbsnbTrpoQ=";

        //then
        assertThatThrownBy(() -> SerializableUtil.<DefaultSerialEntity>deSerializeMethod(serializedByteStrBeforeFieldAdd))
                .isInstanceOf(InvalidClassException.class);
    }

}

 

 

필드 추가 후 정상적으로 역직렬화가 되는 것을 확인하는 테스트 코드

public class SerialUIDEntity implements Serializable {

    @Serial
    private static final long serialVersionUID = -7116771609929351855L;

    private String title;
    private String pressName;
    private String reporterName;

    // 새로운 필드 추가
    private String phoneNumber;

    public SerialUIDEntity(String title, String pressName, String reporterName) {
        this.title = title;
        this.pressName = pressName;
        this.reporterName = reporterName;
    }

    @Override
    public String toString() {
        return "SerialUIDEntity{" +
                "title='" + title + '\'' +
                ", pressName='" + pressName + '\'' +
                ", reporterName='" + reporterName + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                '}';
    }

}

// test code
class SerialUIDEntityTest {

    @Test
    @DisplayName("serialVersionUID 을 선언하면 클래스 정보 관계없이 serialVersionUID 기준으로 직렬화된다.")
    void SerialVersionUID() throws IOException, ClassNotFoundException {
        //given, when
        // phoneNumber 필드 추가 이전 직렬화 값 : new SerialUIDEntity("title222", "pressName222", "reporter222");
        String serializedByteStrBeforeFieldAdd = "rO0ABXNyACdjb20uY29vcGVyLnNlcmlhbGl6YWJsZS5TZXJpYWxVSURFbnRpdHmdPCX" +
                "tUpN9UQIAA0wACXByZXNzTmFtZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wADHJlcG9ydGVyTmFtZXEAfgABTAAFdGl0bGVxAH4AAXh" +
                "wdAAMcHJlc3NOYW1lMjIydAALcmVwb3J0ZXIyMjJ0AAh0aXRsZTIyMg==";

        //then
        Assertions.assertThat(SerializableUtil.<SerialUIDEntity>deSerializeMethod(serializedByteStrBeforeFieldAdd))
                .isInstanceOf(SerialUIDEntity.class);
    }

}

 

3. 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제

  1. 클래스의 구조가 변경 되었을 때 오류가 발생한다.
    • 예시: 멤버 변수 타입 변경멤버 변수 삭제
  2. 클래스의 기본 해시값을 통한 SerialVersionUID 의 문제
    • 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체도 버전업을 통해 SerialVersionUID 가 변경될 경우가 있으므로 예상하지 못한 오류가 발생할 수 있다.

 

4. 🚨 SerialVersionUID 는 되도록이면 개발자가 직접 관리하자!!

  • intellij 에서는 serialVersionUID 자동 생성을 제공하는 옵션을 제공하기 때문에 꼭 옵션을 추가하도록 하자!! (이왕이면 경고까지 띄우도록 하자!)
  • (File > Settings > Serializable class without ‘serialVersionUID’)

 

 

 

5. Serialazable 의 권장하지 않지만 사용할 때는 주의해서 사용하자.

  1. 악의적인 공격 요소가 많다.
    • readObject : 해당 메서드는 클래스 패스에 있고 Serializable interface 를 구현한 모든 타입을 생성할 수 있기 때문이다.
    • 역직렬화 필터링 을 사용해 안전하다고 판단되는 클래스 화이트 리스트 와 같은 목록을 역직렬화를 하는데 제공해야 한다.

 

6. References