java-反序列化基础3——JDK动态代理
代理模式的通用类图
上图中,Subject
是一个抽象类或者接口,RealSubject
是实现方法类,具体的业务执行,Proxy
则是RealSubject
的代理,直接和client
接触的。
静态代理
以租房为例,首先定义一个租房的接口
public interface IRentHouse{
void rentHouse();
}
定义租房的实现类
public class RentHouse implements IRentHouse {
@Override
public void rentHouse() {
System.out.println("租了一间房子。。。");
}
}
再定义一个中介,在中介中传入要租的房子,中介帮你完成租房操作,这就是代理
在中介中也实现了租房的接口
public class IntermediaryProxy implements IRentHouse {
private IRentHouse rentHouse;
public IntermediaryProxy(IRentHouse irentHouse){
rentHouse = irentHouse;
}
@Override
public void rentHouse() {
System.out.println("交中介费");
rentHouse.rentHouse();
System.out.println("中介负责维修管理");
}
}
在main方法中进行测试
public class Main {
public static void main(String[] args){
//定义租房
IRentHouse rentHouse = new RentHouse();
//定义中介
IRentHouse intermediary = new IntermediaryProxy(rentHouse);
//中介租房
intermediary.rentHouse();
}
}
返回信息
交中介费
租了一间房子。。。
中介负责维修管理
总的来说就是一个接口,一个实体实现类,一个代理实现类,一个启动器
静态代理的优缺点
优点:职责清晰,扩展性好,保护真实主题
缺点:代码冗余,维修困难,灵活性差
动态代理
所有的interface
类型的变量总是通过某个实例向上转型并赋值给接口类型变量的:
CharSequence cs = new StringBuilder();
那有没有可能不编写实现类,直接在运行期间创建某个interface
的实例呢?
这是可能的,java标准库提供了一种动态代理机制,可以在运行期间动态创建某个interface
的实例。
一个简单的动态代理实现如下:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
if (method.getName().equals("morning")) {
System.out.println("Good morning, " + args[0]);
}
return null;
}
};
Hello hello = (Hello) Proxy.newProxyInstance(
Hello.class.getClassLoader(), // 传入ClassLoader
new Class[] { Hello.class }, // 传入要实现的接口
handler); // 传入处理调用方法的InvocationHandler
hello.morning("Bob");
}
}
interface Hello {
void morning(String name);
}
//返回结果
// public abstract void org.example.Hello.morning(java.lang.String)
// Good morning, Bob
定义了一个
InvocationHandler
实例,它负责实现接口的方法调用
Object invoke(Object proxy, 方法 method, Object[] args);
proxy
– 调用该方法的代理实例method
-被调用的方法对应的 Method 对象Object[] args
-方法调用时传入的参数数组
通过
Proxy.newProxyInstance()
创建interface
实例,它需要3个参数:
使用的
ClassLoader
,通常就是接口类的ClassLoader;需要实现的接口数组,至少需要传入一个接口进去;
用来处理接口方法调用的
InvocationHandler
实例。
将返回的
Object
强制转型为接口
所以如果用动态代理表示租房过程就是
接口类:
public interface IRentHouse {
public void add();
public void delete();
public void update();
public void query();
}
实体实现类
public class RentHouse implements IRentHouse {
@Override
public void add() {
System.out.println("增加了一个用户");
}
@Override
public void delete() {
System.out.println("删除了一个用户");
}
@Override
public void update() {
System.out.println("更新了一个用户");
}
@Override
public void query() {
System.out.println("查询了一个用户");
}
}
代理实现类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class IntermediaryProxy implements InvocationHandler {
IRentHouse rentHouse;
public void setIntermediaryProxy(IRentHouse iRentHouse) {
this.rentHouse = iRentHouse;
}
// 动态生成代理类实例
public Object getProxy(){
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), rentHouse.getClass().getInterfaces(), this);
return obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method);
System.out.println("调用了"+method.getName());
Object obj=method.invoke(rentHouse,args);
return obj;
}
//业务自定义需求
public void log(Method method){
System.out.println("[Info] " + method.getName() + "方法被调用");
}
}
启动器:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Renttext {
public static void main(String[] args){
RentHouse rentHouse = new RentHouse();
IntermediaryProxy intermediaryProxy=new IntermediaryProxy();
intermediaryProxy.setIntermediaryProxy((IRentHouse) rentHouse);
IRentHouse proxy=(IRentHouse) intermediaryProxy.getProxy();
proxy.add();
proxy.delete();
proxy.query();
proxy.update();
}
}
运行结果:
在反序列化中动态代理的作用
假设存在一个能漏洞利用的类B.f
,比如Runtime.exec
我们将入口类定义为A,最理想的情况是 A[O] -> O.f
,那么我们把B传入A就行,但是A中存在O.abc方法,如果O是一个动态代理的话,O里的invoke
方法中存在.f
的方法,便可以漏洞利用。
相当于
readObject
->反序列化自动执行
invoke
->有函数调用