由于已经知道它很容易地添加 _序列化_支持lambda表达式时,目标接口已经不继承Serializable
,就像(TargetInterface&Serializable)()->{/*code*/}
。
我想要的是一种相反的方法,当目标接口 确实 继承时,显式删除对序列化的支持Serializable
。
由于您无法从类型中删除接口,因此基于语言的解决方案可能看起来像(@NotSerializable TargetInterface)()->{/* code*/}
。但是据我所知,还没有这样的解决方案。(如果我错了,请纠正我,这将是一个完美的答案)
即使Serializable
在过去实现了类的合法行为并且在程序员控制下的类的情况下,也拒绝序列化。
public class NotSupportingSerialization extends SerializableBaseClass {
private void writeObject(java.io.ObjectOutputStream out) throws IOException {
throw new NotSerializableException();
}
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException {
throw new NotSerializableException();
}
private void readObjectNoData() throws ObjectStreamException {
throw new NotSerializableException();
}
}
但是对于lambda表达式,程序员没有对lambda类的控制权。
为什么有人会烦恼取消支持?好吧,除了生成包含Serialization
支持的较大代码之外,它还会带来安全风险。考虑以下代码:
public class CreationSite {
public static void main(String... arg) {
TargetInterface f=CreationSite::privateMethod;
}
private static void privateMethod() {
System.out.println("should be private");
}
}
在这里,只要程序员注意,不要将实例传递给不受信任的代码,即使TargetInterface
is
public
(接口方法始终为public
)也不会公开访问私有方法f
。
但是,如果TargetInterface
继承,情况就会改变Serializable
。然后,即使CreationSite
永不分发实例,攻击者也可以通过反序列化手动构建的流来创建等效实例。如果上面示例的界面看起来像
public interface TargetInterface extends Runnable, Serializable {}
就像这样简单:
SerializedLambda l=new SerializedLambda(CreationSite.class,
TargetInterface.class.getName().replace('.', '/'), "run", "()V",
MethodHandleInfo.REF_invokeStatic,
CreationSite.class.getName().replace('.', '/'), "privateMethod",
"()V", "()V", new Object[0]);
ByteArrayOutputStream os=new ByteArrayOutputStream();
try(ObjectOutputStream oos=new ObjectOutputStream(os)) { oos.writeObject(l);}
TargetInterface f;
try(ByteArrayInputStream is=new ByteArrayInputStream(os.toByteArray());
ObjectInputStream ois=new ObjectInputStream(is)) {
f=(TargetInterface) ois.readObject();
}
f.run();// invokes privateMethod
请注意,攻击代码不包含a SecurityManager
将会撤销的任何操作。
支持序列化的决定是在编译时做出的。它需要添加一个合成工厂方法,CreationSite
并将标记传递给元工厂方法。没有该标志,即使接口碰巧继承了,生成的lambda也将不支持序列化Serializable
。lambda类甚至具有上面示例中的writeObject
方法NotSupportingSerialization
。如果没有合成工厂方法,则无法进行反序列化。
我找到了一种解决方案。您可以创建接口的副本,并将其修改为不继承Serializable
,然后针对该修改后的版本进行编译。因此,当在运行时继承真实版本时Serializable
,序列化仍将被撤销。
好吧,另一种解决方案是,至少不要在安全相关代码中使用lambda表达式/方法引用,至少是在Serializable
针对较新版本的接口进行编译时,如果目标接口继承了该接口,则必须始终对其进行重新检查。
但是我认为必须有更好的,最好是语言内的解决方案。
EG面临的最大挑战之一是如何处理可序列化性。可以说,没有伟大的解决方案,只有各种弊端之间的权衡。一些团体坚持认为所有lambda都可以自动序列化(!);其他人则坚持认为lambda永远不可序列化(这有时看起来很吸引人,但可悲的是,这将严重违反用户的期望。)
您注意:
嗯,另一种解决方案是永远不要在与安全相关的代码中使用lambda表达式/方法引用,
实际上,序列化规范现在已明确说明了这一点。
但是,有一个相当简单的技巧可以在这里执行您想要的操作。假设您有一些需要可序列化实例的库:
public interface SomeLibType extends Runnable, Serializable { }
使用期望这种类型的方法:
public void gimmeLambda(SomeLibType r)
并且您想将lambda传递给它,但又不想让它们可序列化(并承担其后果。)因此,请编写此辅助方法:
public static SomeLibType launder(Runnable r) {
return new SomeLibType() {
public void run() { r.run(); }
}
}
现在您可以调用库方法:
gimmeLambda(launder(() -> myPrivateMethod()));
编译器会将您的lambda转换为不可序列化的Runnable,并且清洗程序包装器将使用满足类型系统的实例对其进行包装。当您尝试对其进行序列化时,由于r
无法序列化,因此将失败。更重要的是,您不能伪造对私有方法的访问,因为捕获类中所需的$deserializeLambda
$
支持甚至都不存在。
众所周知,当目标接口尚未继承可序列化的时,很容易向lambda表达式添加序列化支持,就像 我要求的是一种相反的方法,即在目标接口继承时显式删除序列化支持。 由于无法从类型中删除接口,基于语言的解决方案可能看起来像是(@NotSerializable TargetInterface)()- 即使在类实现可序列化时拒绝序列化在过去是一种合法的行为,并且类在程序员的控制下,模式如下: 但是对于lambd
问题内容: 我收到以下消息: [#| 2010-07-30T11:28:32.723 + 0000 |警告| glassfish3.0.1 | javax.faces | _ThreadID = 37; _ThreadName = Thread-1; |将不可序列化的属性值设置为ViewMap:((键:MyBackingBean,值类:foo.bar.org.jsf.MyBackingBean)|
我想缓存来自第三方API的响应,该API提供自己的客户端和数据类。但问题是,这两个数据类都没有实现接口,因此使用Spring Boot Cache Redis进行缓存时,在我尝试时抛出。 是否可以通过某种方式将Spring Boot Redis缓存配置为允许缓存未实现可序列化的对象?
我在搞lambdas的Java序列化。 我有两个完全独立的项目,其中只有一个类。 项目1: 项目二: 但是,在尝试反序列化可运行文件时,它会抛出一个异常,说它找不到 ...我有什么办法可以避免这种情况吗?
我想将前面文章中提到的要求扩展到支持删除。我们有两个数据模型对象组织和部门共享一对多的关系。通过下面的映射,我可以从organization对象读取部门列表。我没有添加cascade ALL属性来限制在创建组织时添加部门。
我正在试验Stanford CoreNLP库,我想序列化主要的StanfordCoreNLP管道对象,尽管它抛出了一个java.io.NotSerializableException。 完整故事:每当我运行我的实现时,将管道注释器和分类器加载到内存中大约需要15秒。最终进程的内存约为600MB(很容易小到可以存储在我的机箱中)。我想在第一次创建管道后保存它,这样我就可以在以后将其读入内存。 然而,