[Java]Serialization(직렬화) 왜 필요할까?

JANG SEONG SU·2023년 7월 29일
0

Java

목록 보기
8/10

1. Serialization(직렬화)이란?

직렬화(serialize)란 자바 언어에서 사용되는 Object 또는 Data를 다른 컴퓨터의 자바 시스템에서도 사용 할수 있도록 바이트 스트림(stream of bytes) 형태로 연속전인(serial) 데이터로 변환하는 포맷 변환 기술을 일컫는다.
그 반대 개념인 역직렬화는(Deserialize)는 바이트로 변환된 데이터를 원래대로 자바 시스템의 Object 또는 Data로 변환하는 기술이다.

실제 JVM의 힙(Heap)이나 스택(Stack)에 있는 객체 데이터는 바이트 형태로 저장되어 있다.


2. Serialization 사용 예제

예제1

우선 직렬화하려는 Object class에 Serializable 인터페이스를 상속해야한다.

class Person implements Serializable {
    String name;
    String job;
//    transient String job;     //transient 선언시,job의 값을 직렬하는 대신,
								//String의 default값인 null이 직렬화된다. 
    public Person() { }			//반드시 Default 생성자가 있어야한다.

    public Person(String name, String job) {
        this.name = name;
        this.job = job;
    }

   public String toString(){
        return name + ", " + job;
   }

}

다음은 Person 객체를 생성한 후 직렬화하는 과정이다.

public static void main(String[] args) {

    Person John = new Person("John", "Doctor");
    Person Kim = new Person("Kim", "Lawyer");

    try(FileOutputStream fos = new FileOutputStream("serial.txt");       //try( ) 괄호 안에 Closable 객체 넣으면 자동으로 close해줌
        ObjectOutputStream oos = new ObjectOutputStream(fos)) {			//직렬화(스트림에 객체를 출력) 에는 ObjectOutputStream을 사용

            oos.writeObject(John);      //write 할 때는 객체를 Serialize 해주어야함,
            oos.writeObject(Kim);       //이 때, 객체를 Serialize 하기 위해서는 그 객체 클래스에 Serializable 인스턴스를 상속받아야함

    } catch(IOException e ){
        e.printStackTrace();
    }

    try(FileInputStream fis = new FileInputStream("serial.txt"); 
        ObjectInputStream ois = new ObjectInputStream(fis)) {		//역직렬화(스트림으로부터 객체를 입력)에는 ObjectInputStream을 사용

            Person p1 = (Person)ois.readObject();	//반드시 같은 클래스로 캐스팅을 해야한다.
            Person p2 = (Person)ois.readObject();

            System.out.println(p1);
            System.out.println(p2);

    } catch(IOException e ){
        e.printStackTrace();
    } catch(ClassNotFoundException e) {
        e.printStackTrace();
    }
}

예제2

직렬화 방식을 (writeExternal/readExternal) 혹은 (readObject/writeObjecdt)를 이용하여 커스텀할 수 있다.

이때는 Serializable 인터페이스를 상속받고 있는 클래스만 수정하면 된다.

class Person implements Externalizable {       
	String name;
    String job;

    public Person() {}     //디폴트 생성자 반드시 있어야함

    public Person(String name, String job) {
        this.name = name;
        this.job = job;
    }

   public String toString(){
        return name + ", " + job;
   }

    @Override		//wirteObject 사용 가능
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeUTF(name);
//        out.writeUTF(job);
    }

    @Override		//readObject 사용 가능
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        name = in.readUTF();
//        job = in.readUTF();
    }
}

/* **출력** */
//John, null
//Kim, null

3. Json vs Serialization

JSON 이라는 훌륭한 데이터 포맷이 있는데 굳이 직렬화가 필요할까라는 의문이 든다.
JSON은 Java뿐만 아니라 Python, JSP(JavaScript)에서도 사용이 가능하지만, 직렬화는 오로지 자바 프로그램에서만 사용할 수 있어 범용이 제한적이다.

✴그럼에도 불구하고, 직렬화를 쓰는 이유는 무엇일까
✔ 직렬화는 자바의 고유 기술인 만큼 당연히 자바 시스템에서 개발에 최적화되어 있다.
✔ 직렬화는 자바의 광활한 레퍼런스 타입에 대해 제약 없이 외부에 내보낼 수 있다는 것이다.

🔍예를들어 기본형(int, double, string) 타입이나 배열(array)과 같은 타입들은 왠만한 프로그래밍 언어가 공통적으로 사용하는 타입이기 때문에, 이러한 값들을 JSON으로도 충분히 상호 이용이 가능하다.

하지만 자바의 온갖 컬렉션이나 클래스, 인터페이스 타입들은 Json으로 변환하기에는 한계가 있다.

반면에 직렬화는 이러한 한계 없이, 복잡한 클래스를 비교적 쉽게 직렬화/역직렬화하여 사용할 수 있다.


4. Serialization 취약점

하지만 Serialization의 문제점은 다음과 같다.

1. 직렬화는 용량이 크다
직렬화는 객체에 저장된 데이터값 뿐만 아니라 타입 정보, 클래스 메타 정보를 가지고 있으므로 용량을 은근히 많이 차지한다. 그래서 같은 정보를 직렬화로 저장하느냐 JSON으로 저장하느냐는 파일 용량 크기가 거의 2배 이상 차이가 난다.

따라서 DB, Cache 등에 외부에 저장할때, 장기간 동안 저장하는 정보는 직렬화를 지양해야 된다.

2. 릴리즈 후에 수정이 어렵다
클래스가 Serializable을 구현하게 되면 직렬화된 바이트 스트림 인코딩도 하나의 공개 API가 되는 것이다. 그래서 직렬화을 구현한 클래스가 널리 퍼지면 그 직렬화 형태도 영원히 지원해야한다. 클래스의 내부 구현을 수정한다면 원래의 직렬화 형태와 달라지게 되기 때문이다.

즉, Serializable을 구현한 순간부터 해당 객체의 유지보수는 직렬화에 묶이게 되는 것이다.

3. 클래스 캡슐화가 깨진다
만일 직렬화할 클래스에 private 멤버가 있어도 직렬화를 하게 되면 그대로 외부로 노출되게 된다. (직렬화를 제외하려면 별도로 transient 설정해야 된다)

따라서 Serializable을 구현하면 직렬화 형태가 하나의 공개 API가 되어 캡슐화가 깨지게 된다.

4. 보안에 취약하다
직렬화 설정 자체는 문제는 없지만, 남이 만든 것을 역직렬화 과정에서 나도 모르게 공격당할 위험성이 있다.

역직렬화 과정에서 호출되어 잠재적으로 위험한 동작을 수행하는 메서드를 가젯(gadget) 이라고 부르는데, 바이트 스트림을 역직렬화하는 ObjectInputStream의 readObject() 메서드를 호출하게 되면 객체 그래프가 역직렬화되어 classpath 안의 모든 타입의 객체를 만들어 내게 되는데, 해당 타입 객체안의 모든 코드를 수행할수 있게 되므로 나의 프로그램 코드 전체가 공격 범위에 들어가게 된다.


profile
Software Developer Lv.0

0개의 댓글