직렬화(Serialization)는 객체를 바이트 스트림(Byte Stream)으로 변환하여, 이를 파일로 저장하거나 네트워크를 통해 전송할 수 있게 하는 과정입니다.
반대로, 역직렬화(Deserialization)는 바이트 스트림을 다시 객체로 복원하는 과정입니다.
Java에서는 직렬화를 통해 객체의 상태를 저장하거나 다른 시스템으로 데이터를 전달할 수 있습니다.
이번 글에서는 직렬화에 대해 알아보겠습니다.🧐
직렬화의 필요성
자바 프로그램에서는 메모리 내에서만 객체를 사용할 수 있습니다.
하지만 때로는 객체를 파일로 저장하거나 네트워크를 통해 전송할 필요가 있습니다.
이때 객체의 상태를 바이트 스트림으로 변환하여 저장하거나 전송할 수 있는데, 이 과정이 바로 직렬화입니다.
예를 들어, 한 객체의 데이터를 파일에 저장해 프로그램이 종료된 후에도 다시 불러와야 할 경우, 직렬화를 사용하면 쉽게 객체를 저장하고 다시 복원할 수 있습니다.
직렬화의 흐름
객체를 직렬화
자바의 'ObjectOutputStream'을 사용해 객체를 바이트 스트림으로 변환합니다.
이때, 객체의 상태(필드 값)가 전부 직렬화되어 스트림에 기록됩니다.
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("objectData.ser"));
out.writeObject(someObject);
out.close();
역직렬화
바이트 스트림을 객체로 변환하는 과정입니다.
'ObjectInputStream'을 사용하여 직렬화된 데이터를 다시 객체로 복원합니다.
ObjectInputStream in = new ObjectInputStream(new FileInputStream("objectData.ser"));
SomeClass someObject = (SomeClass) in.readObject();
in.close();
직렬화의 기본 구현
Java에서 Java.io.Serializable 인터페이스를 통해 직렬화를 구현합니다.
이 인터페이스는 특별한 메서드를 포함하지 않지만, 객체를 직렬화할 수 있음을 자바 런타임에 알려주는 역할을 합니다.
import java.io.*;
// Serializable 인터페이스를 구현하여 직렬화 가능하도록 설정
class Person implements Serializable {
private static final long serialVersionUID = 1L; // 직렬화 버전 관리용 ID
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class SerializationExample {
public static void main(String[] args) {
Person person = new Person("John Doe", 30);
// 객체를 직렬화하여 파일에 저장
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.ser"))) {
oos.writeObject(person);
System.out.println("직렬화 완료!");
} catch (IOException e) {
e.printStackTrace();
}
// 파일에서 객체를 역직렬화하여 다시 읽기
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.ser"))) {
Person deserializedPerson = (Person) ois.readObject();
System.out.println("역직렬화 완료! 이름: " + deserializedPerson.name + ", 나이: " + deserializedPerson.age);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
주요 특징
- Serializable 인터페이스
직렬화 가능 객체를 만들기 위해서는 Serializable 인터페이스를 반드시 구현해야 합니다.
이 인터페이스는 메서드가 없기 때문에 구현하는 클래스에 추가적인 구현을 요구하지 않습니다. - serialVersionUID
자바 직렬화에서 클래스가 변경되면 역직렬화 시 오류가 발생할 수 있습니다.
이를 방지하기 위해 serialVersionUID라는 고유 ID를 부여하여 클래스 버전 관리를 할 수 있습니다.
만약 serialVersionUID를 명시하지 않으면 자바가 자동으로 생성하지만, 수동으로 명시해 주는 것이 좋습니다. - transient 키워드
직렬화하고 싶지 않은 필드가 있을 경우, 해당 필드에 transient 키워드를 붙이면 그 필드는 직렬화 대상에서 제외됩니다.
class Person implements Serializable {
String name;
transient int age; // 직렬화되지 않음
}
직렬화의 활용 사례
- 세션 관리
웹 애플리케이션에서 세션 상태를 직렬화하여 저장할 수 있습니다.
서버가 재시작되거나 세션 상태를 다른 서버로 전달해야 할 때 유용합니다. - 데이터베이스 캐시
객체를 직렬화하여 캐시 시스템에 저장하고, 필요할 때 다시 객체로 역직렬화하여 빠르게 접근할 수 있습니다. - 네트워크 통신
객체를 바이트 스트림으로 변환하여 네트워크를 통해 다른 서버나 클라이언트로 전송할 때 직렬화를 활용할 수 있습니다.
직렬화의 위험성
자바 직렬화는 유용하지만, 보안적인 위험이 존재합니다.
특히 역직렬화 과정에서 외부의 바이트 스트림을 처리할 때, 악의적인 데이터가 포함될 수 있습니다.
이로 인해 시스템에 해킹이나 원격 코드 실행 등의 심각한 문제가 발생할 수 있습니다.
주요 위험 요소
- 역직렬화 취약점
직렬화된 바이트 스트림이 조작되면, 악성 객체를 생성하여 시스템을 공격할 수 있습니다.
특히, 역직렬화 과정에서 예기치 않은 클래스가 로드되면서 악의적인 코드를 실행할 가능성이 큽니다. - 클래스 변경 시 문제
클래스 구조가 변경되었을 때(ex: 필드 추가/삭제), 이전에 직렬화된 객체를 다시 읽어올 때 불일치로 인한 호환성 문제가 발생할 수 있습니다.
직렬화의 방어 방법
serialVersionUID 사용
클래스 변경에 따른 호환성 문제를 방지하기 위해, serialVersionUID를 명시적으로 선언해야 합니다.
serialVersionUID는 클래스 버전 관리를 위한 고유 식별자 역할을 하며, 클래스가 변경되더라도 일관성을 유지할 수 있습니다.
private static final long serialVersionUID = 1L;
신뢰할 수 없는 데이터 역직렬화 방지
역직렬화 과정에서 외부 입력을 검증하는 것이 중요합니다.
Java 9부터는 ObjectInputFilter를 도입해 역직렬화 시 필터링을 적용할 수 있습니다.
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter("com.example.*;!*");
ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.ser"));
in.setObjectInputFilter(filter);
역직렬화를 피하기
직렬화 대신 더 안전한 데이터 포맷(JSON, XML 등)을 사용하는 것이 일반적으로 권장됩니다.
이를 통해 데이터 구조가 명확하게 정의되고, 보안 위험을 줄일 수 있습니다.
서명된 객체 검증
객체를 직렬화할 때, 디지털 서명을 통해 데이터의 무결성을 보장할 수 있습니다.
직렬화된 데이터에 서명을 추가하여 역직렬화 시 데이터가 위변조 되지 않았음을 확인할 수 있습니다.
💡 Java 직렬화는 객체를 저장하고 전송하는 데 매우 유용하지만, 보안적인 취약점이 있습니다.
이러한 위험을 최소화하기 위해서는 serialVersionUID를 명시적으로 정의하고, 역직렬화 필터를 사용하거나 안전한 데이터 포맷을 선택하는 것이 중요합니다.
'Java' 카테고리의 다른 글
[Java] @SuperBuilder란? (0) | 2024.10.17 |
---|---|
[Java] String.valueOf()와 toString() 차이점 (0) | 2024.10.14 |
[Java] 람다 표현식(Lambda Expressions) (0) | 2024.09.16 |
[Java] 상속(Inheritance)보다는 컴포지션(Composition) (0) | 2024.08.24 |
[Java] JPA의 @Lock 동시성 제어 (0) | 2024.08.12 |