Cơ bản về Serialization và Deserialization với Java
Trước khi nghiên cứu về Insecure Deserialization với Java, chúng ta cần tìm hiểu xem Serialization và Deserialization là gì. Bài viết này là để mình tổng hợp lại một cách ngắn gọn và dễ hiểu nhất.
Các kiến thức cần biết trước khi tìm đến Serialization và Deserialization
Nếu bạn là người mới hoàn toàn trong lập trình hướng đối tượng với Java, bạn nên học từ OOP cơ bản để nắm được các kiến thức về 4 tính chất quan trọng của lập trình hướng đối tượng, biết về các Abstract Class và Interface…
Kiến thức cần biết thêm ở đây là kỹ thuật Reflection trong Java. Mình sẽ có một bài viết riêng về phần này. Trước mắt các bạn có thể tham khảo bài viết này:
Vậy là đã đủ nền tảng để các bạn có thể bước vào bài viết này.
Tại sao cần Serialization và Deserialization?
Các định dạng như JSON, XML chỉ cho phép lưu các kiểu dữ liệu cơ bản. Trong khi đó Serialization hỗ trợ việc lưu trữ và truyền tải các đối tượng phức tạp, bao gồm cả các quan hệ đối tượng, kế thừa, đa hình và các tính năng nâng cao khác, đặc biệt trong ngôn ngữ Java. Nhờ cơ chế này mà object states có thể được lưu trữ ở trong cùng 1 môi trường hay cả 2 môi trường khác nhau. Cơ chế này được sử dụng rộng rãi trên các công nghệ như EJB, RMI, Hessian.
Khái niệm
Serialization
Serialization là cơ chế chuyển đổi cấu trúc dữ liệu (data structure) hoặc trạng thái của đối tượng (object states) trở thành luồng byte (byte streams). Byte streams có thể được lưu vào file, bộ nhớ đệm (memory buffering), database....
Ta có một object User như này:
import java.io.Serializable;
public class User implements Serializable {
String name;
transient String password;
public void sayHello() {
System.out.println("Hello, " + name + "!");
}
}
Để serialize một đối tượng, bạn cần phải đảm bảo rằng class của đối tượng đó implements java.io.Serializable
interface. Serializable interface chỉ là một marker interface, bản thân nó chẳng định nghĩa 1 phương thức nào cả , nó chỉ có nhiệm vụ chỉ ra đây là đối tượng có thể serialized được.
Một đoạn mã thực hiện Serialize cơ bản sẽ như sau:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
public class Main {
public static void main(String[] args) {
User user = new User();
user.name = "Tung";
user.password = "12345";
try {
FileOutputStream fileOut = new FileOutputStream("userinfo.ser");
ObjectOutputStream out = new ObjectOutputStream(fileOut);
out.writeObject(user);
out.close();
fileOut.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
user.sayHello();
}
}
Đoạn mã này khởi tạo một đối tượng fileOut
từ class FileOutputStream
, dùng để tạo một luồng ghi (output stream) vào một tệp (userinfo.ser) dưới dạng nhị phân.
Tiếp đó tạo một đối tượng out
từ lớp ObjectOutputStream
, chuyển đổi các đối tượng trong Java thành dạng nhị phân, sử dụng fileOut
làm đích đến cho các dữ liệu đối tượng. Điều này có nghĩa là ObjectOutputStream
sẽ ghi dữ liệu vào fileOut
.
Nội dung của file userinfo.ser
khi được mở bằng hex editor có dạng như này:
0xACED
: STREAM_MAGIC. Chỉ ra đây là dữ liệu dạng serialize.
Deserialization
Deserialization là cơ chế ngược lại với Serialization, cho phép khôi phục lại object states thông qua việc phân tích byte streams được tạo ra sau khi Serialization.
Đoạn mã deserialize để khôi phục lại đối tượng user
như sau:
import java.io.*;
public class Main {
public static void main(String[] args) {
User user = null;
try {
FileInputStream fileIn = new FileInputStream("userinfo.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
user = (User) in.readObject();
in.close();
fileIn.close();
} catch (IOException | ClassNotFoundException e) {
throw new RuntimeException(e);
}
user.sayHello();
}
}
Đối tượng fileIn
được tạo từ lớp FileInputStream
để đọc file userinfo.ser
. Một đối tượng in
từ lớp ObjectInputStream
cũng được tạo để đọc dữ liệu byte từ fileIn
và chuyển đổi nó về dạng đối tượng bằng phương thức readObject()
.
Một số lưu ý quan trọng
Các thuộc tính
static
sẽ không được serialize vì nó là thuộc tính của lớp.Các thuộc tính
transient
cũng không được serialize.Một thuộc tính quan trọng là serialVersionUID được dùng để xác nhận rằng cả người nhận và người gửi đã cùng sử dụng đúng class cho đối tượng được serialize và deserialize.
Nếu bên nhận (receiver) đã load một class (giả sử class A) cho object có SUID khác với class tương ứng (cũng là class A) của bên gửi (sender) thì khi deserialize sẽ dẫn đến
InvalidClassException
.Field serialVersionUID phải là static, final và kiểu dữ liệu long. Và thường được khuyến nghị là nên set private khi có thể.
Nếu một class (Serializable) không khai báo serialVersionUID một cách rõ ràng, thì Serialization runtime tự sẽ tính toán một giá trị mặc định cho class đó.
Tài liệu tham khảo
Serialization và Deserialization trong Java với Ví dụ | GeeksforGeeks
Serialization và Deserialization trong java - tại sao lại cần thiết | Viblo