一、Java序列化与反序列化的工作原理
Java序列化就是把Java对象转换为字节序列的过程,反序列化则是把字节序列恢复为Java对象的过程。这就像把一本书变成电子文件(序列化),需要时再打印成纸质书(反序列化)。
// 技术栈:Java原生序列化
import java.io.*;
// 可序列化的用户类
class User implements Serializable {
private static final long serialVersionUID = 1L;
private String username;
private transient String password; // transient修饰的字段不会被序列化
public User(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public String toString() {
return "User{username='" + username + "', password='" + password + "'}";
}
}
public class SerializationDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// 序列化
User user = new User("admin", "123456");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(user);
oos.close();
byte[] bytes = bos.toByteArray();
// 反序列化
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
User deserializedUser = (User) ois.readObject();
System.out.println(deserializedUser); // 输出:User{username='admin', password='null'}
}
}
从示例中可以看到,password字段因为被transient修饰,反序列化后变成了null。serialVersionUID用于版本控制,如果序列化和反序列化的类版本不一致,会抛出InvalidClassException。
二、常见的安全风险及攻击方式
1. 反序列化漏洞攻击
攻击者可以构造恶意的序列化数据,当程序反序列化这些数据时,会执行攻击者预设的危险操作。这就像收到一个看似普通的快递,打开后却发现是炸弹。
// 危险示例:可能被利用的类
class Dangerous implements Serializable {
private String command;
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
Runtime.getRuntime().exec(command); // 反序列化时执行系统命令
}
}
2. 数据篡改风险
序列化数据可以被中间人修改,导致反序列化后得到错误或危险的对象。比如把用户角色从"普通用户"改为"管理员"。
3. 拒绝服务攻击
攻击者可以发送特别构造的序列化数据,消耗大量服务器资源。例如包含深度嵌套的对象图,导致堆栈溢出。
三、防护措施与实践方案
1. 使用安全的替代方案
JSON和XML等格式比Java原生序列化更安全:
// 技术栈:Jackson JSON库
import com.fasterxml.jackson.databind.ObjectMapper;
public class JsonSerialization {
public static void main(String[] args) throws IOException {
User user = new User("admin", "123456");
ObjectMapper mapper = new ObjectMapper();
// 序列化为JSON
String json = mapper.writeValueAsString(user);
System.out.println(json); // 输出:{"username":"admin"}
// 从JSON反序列化
User deserialized = mapper.readValue(json, User.class);
System.out.println(deserialized); // 输出:User{username='admin', password='null'}
}
}
2. 白名单验证
只允许反序列化可信的类:
public class SecureObjectInputStream extends ObjectInputStream {
private static final Set<String> ALLOWED_CLASSES =
Set.of("java.lang.String", "com.example.User");
public SecureObjectInputStream(InputStream in) throws IOException {
super(in);
}
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
if (!ALLOWED_CLASSES.contains(desc.getName())) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return super.resolveClass(desc);
}
}
3. 加密和签名
对序列化数据进行加密和签名,防止篡改:
// 技术栈:Java加密体系
import javax.crypto.*;
import java.security.*;
import java.util.Base64;
public class SecureSerialization {
public static void main(String[] args) throws Exception {
// 生成密钥
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256);
SecretKey secretKey = keyGen.generateKey();
User user = new User("admin", "123456");
// 序列化+加密
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
CipherOutputStream cos = new CipherOutputStream(bos, cipher);
ObjectOutputStream oos = new ObjectOutputStream(cos);
oos.writeObject(user);
oos.close();
String encrypted = Base64.getEncoder().encodeToString(bos.toByteArray());
// 解密+反序列化
byte[] decoded = Base64.getDecoder().decode(encrypted);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
CipherInputStream cis = new CipherInputStream(new ByteArrayInputStream(decoded), cipher);
ObjectInputStream ois = new ObjectInputStream(cis);
User decryptedUser = (User) ois.readObject();
System.out.println(decryptedUser);
}
}
四、应用场景与最佳实践
1. 适用场景
- 远程方法调用(RMI)
- 会话持久化
- 深度复制对象
- 缓存对象到磁盘
2. 不适用场景
- 跨语言通信
- 长期数据存储
- 不受信的数据源
3. 最佳实践
- 尽量避免使用Java原生序列化
- 如果必须使用,实现严格的类白名单
- 对敏感字段使用transient修饰
- 保持serialVersionUID的一致性
- 考虑使用第三方安全库如SerialKiller
4. 关联技术:Kryo序列化
Kryo是高效的Java序列化框架,比Java原生序列化更快更安全:
// 技术栈:Kryo库
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
public class KryoSerialization {
public static void main(String[] args) throws Exception {
Kryo kryo = new Kryo();
kryo.register(User.class);
User user = new User("admin", "123456");
// 序列化
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Output output = new Output(bos);
kryo.writeObject(output, user);
output.close();
byte[] bytes = bos.toByteArray();
// 反序列化
Input input = new Input(new ByteArrayInputStream(bytes));
User deserialized = kryo.readObject(input, User.class);
input.close();
System.out.println(deserialized);
}
}
五、总结与建议
Java序列化虽然方便,但存在严重安全隐患。在必须使用的场景下,应采取严格的安全措施。对于新项目,建议优先考虑JSON、Protocol Buffers等更安全的替代方案。安全无小事,特别是在处理不受信任的数据源时,必须格外谨慎。
评论