目录

  1. 1. 前言
  2. 2. 静态代理
  3. 3. 动态代理
    1. 3.1. JDK动态代理
    2. 3.2. CGLIB动态代理
  4. 4. 和反序列化的关系

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

Java代理模式

2023/7/15 Basic Java
  |     |   总文章阅读量:

前言

参考文章:

ph0ebus的博客

先知社区文章

《Java开发实战经典》(学校的教材)

Boogipop的博客

Proxy Pattern是程序设计中的一种设计模式,又称委托模式。代理对象对真实对象提供一种代理以控制其他对象对这个对象的访问。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。代理对象具备真实对象的功能,并代替真实对象完成相应操作,并能够在操作执行的前后,对操作进行增强处理

举个例子:客户通过网络代理连接网络,由代理服务器完成用户权限、访问限制等与上网操作相关的操作,而用户只关心核心业务——上网。代码实现上只需要定义一个上网的接口,代理主题和真实主题都同时实现此接口,再由代理操作真实主题即可。

image-20231209125705098


静态代理

用前面说的场景写个demo:

interface Network {
    // 定义网络接口
    public void browse();
}

// 真实上网操作
class Real implements Network {
    @Override
    public void browse() {
        System.out.println("上网冲浪");
    }
}

// 代理上网
class Proxy implements Network {
    private Network network;

    public Proxy(Network network) {
        this.network = network;
        System.out.println("猫猫使用了神秘的抛瓦!");
    }

    public void check() {
        System.out.println("🈲真人面对面收割,美女角色在线掉分,发狂玩蛇新天地🈲");	// 与具体上网相关的操作
    }
	
    @Override
    public void browse() {
        this.check();	// 调用具体业务
        this.network.browse();	// 
    }
}

public class Static {
    public static void main(String[] args) {
        Network net = null;
        net = new Proxy(new Real());	// 实例化代理,同时传入真实操作
        net.browse();	// 客户只关心核心业务——上网
    }
}

image-20231209132010047

这就是一个完整的静态代理,Proxy类就相当于中间代理商,我们可以通过它来间接调用真实上网操作即Real类中的方法

在静态代理实现中,一个真实类对应一个代理类,并且代理类在编译期间就已经确定

所以静态代理有个缺点,假如我们修改接口,那么Real类和Proxy类都需要修改,这就显得很麻烦,因此动态代理诞生了


动态代理

动态代理的代理类是自动生成的,不是我们直接写好的

动态代理分为三大类:

  • 基于接口的动态代理——JDK的动态代理
  • 基于类的动态代理——Cglib
  • java字节码实现——Javassist

JDK动态代理

动态代理机制需要的接口和类:

java.lang.reflect.Proxy类:代理

有如下操作方法:

public static Object newProxyInstance(ClassLoader loader,	// 类加载器
                                      Class<?>[] interfaces,	// 得到全部的接口
                                      InvocationHandler h)	// 得到InvocationHandler接口的子类实例
    throws IllegalArgumentException

通过这个方法就可以动态地生成实现类

那么如果我们要进行动态代理,首先就要定义一个InvocationHandler接口的子类来完成代理的具体操作

java.lang.reflect.InvocationHandler接口:调用处理程序

public interface InvocationHandler {
    public Object invoke(
        Object proxy,   // 核心业务对象。代理后的对象(被代理的对象),而不是原始对象
        Method method,  // 核心业务。通过invoke方法调用
        Object[] args   // 核心业务的参数。调用方法是传递的实参
    )throws Throwable;
}  // 返回值:附加业务要与核心业务的返回值相同

所以动态代理的编程就分为三步:

  1. 创建原始对象
  2. 完成 InvocationHandler 代理
  3. 调用 Proxy.newProxyInstance

现在对前面静态代理的代码进行修改

demo:

import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

interface Network {
    // 定义网络接口,即目标类要完成的功能
    public void browse();
}

// 真实上网
class Real implements Network {
    @Override
    public void browse() {
        System.out.println("上网冲浪");
    }
}

// 代理类的调用处理器
class MyInvocationHandler implements InvocationHandler {
    private Object obj; // 真实主题

    public MyInvocationHandler(Object obj) {    // 绑定真实操作主题
        this.obj = obj;
    }

    // 此函数在代理对象调用任何一个方法时都会被调用
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 定义预处理的工作,可以根据 method 的不同进行不同的预处理工作
        System.out.println("正在选择端口...");
        System.out.println("正在选择匹配模式...");
        System.out.println("正在选择节点...");

        Object invoke = method.invoke(obj, args);

        System.out.println("提供后续业务...");
        return invoke;
    }
}

public class Dynamic {
    public static void main(String[] args) {
        // 1.创建原始对象
        Network net = new Real();
        // 2.创建调用处理器对象
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(net);
        // 3.动态生成代理对象
        Network netproxy = (Network) Proxy.newProxyInstance(
                Dynamic.class.getClassLoader(),
                new Class[] { Network.class },
                myInvocationHandler);
        // 4.客户端通过代理对象调用方法,本次调用将自动被代理处理器的invoke方法接收
        netproxy.browse();
    }
}

image-20231209180026933


CGLIB动态代理

CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成,在使用时需要导入相应 jar 包

和 JDK 动态代理不同,CGLIB 通过继承方式实现代理。CGLIB通过继承目标类,创建它的子类,在子类中重写父类中非 final 的方法,实现功能的修改。


和反序列化的关系

参考:https://drun1baby.top/2022/06/01/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%9F%BA%E7%A1%80%E7%AF%87-04-JDK%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86/#0x02-%E5%9C%A8%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B8%AD%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E7%9A%84%E4%BD%9C%E7%94%A8

我们先假设存在一个能够漏洞利用的类为 B.f,比如 Runtime.exec 这种。
我们将入口类定义为 A,我们最理想的情况是 A[O] -> O.f,那么我们将传进去的参数 O 替换为 B 即可。但是在实战的情况下这种情况是极少的。

回到实战情况,比如我们的入口类 A 存在 O.abc 这个方法,也就是 A[O] -> O.abc;而 O 呢,如果是一个动态代理类,Oinvoke 方法里存在 .f 的方法,便可以漏洞利用了:

A[O] -> O.abc
O[O2] invoke -> O2.f // 此时将 B 去替换 O2
最后  ---->
O[B] invoke -> B.f // 达到漏洞利用效果

动态代理在反序列化当中的利用和 readObject 是异曲同工的:readObject 方法在反序列化当中会被自动执行,而 invoke 方法在动态代理当中会自动执行