Java代理模式
为什么要学习代理模式?了解开发原理,才能明白漏洞的产生。这不仅仅是SpringAOP的底层!
[SpringAOP 和 SpringMVC]
代理模式的分类:
- 静态代理
- 动态代理
静态代理
角色分析:
- 抽象角色:一般会使用接口或者抽象类来解决
- 真实角色:被代理的角色
- 代理角色:代理真是角色,代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象的人
代码步骤
接口
Rent.java
package Spring_Proxy.Demo01;
//租房的接口
public interface Rent {
public void rent();
}
真实角色
Host.java
- 这是一个类,这个类就是房东,做为房东,他需要租房的接口,也就是Rent.java
package Spring_Proxy.Demo01;
//房东
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
代理角色
Proxy.java:这是一个类,这个类是中介,也就是代理,他需要有房东的房源,这里将房东类作为一个私有属性host,使用host.rent()来实现房东类的rent租房方法
package Spring_Proxy.Demo01;
public class Proxy implements Rent{
//房东的事情
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
//代理的事情
@Override
public void rent() {
host.rent();
seeHouse();
hetong();
fare();
}
//看房
public void seeHouse(){
System.out.println("中介带你看房");
}
public void hetong(){
System.out.println("签租凭合同");
}
//收中介费
public void fare(){
System.out.println("收中介费");
}
}
客户端访问代理角色
Client.java
- 这是一个启动类,因为这里有main方法,这个类就是租客,租客需要先去找中介,然后才能租房,不能直接找房东是因为房东不想那么麻烦。
package Spring_Proxy.Demo01;
public class Client {
public static void main(String[] args) {
//房东要租房子
Host host = new Host();
//代理,中介帮房东租房子,但是呢?代理角色一般会有一些附属操作!
Proxy proxy = new Proxy(host);
//你不用面对房东,直接找中介租房即可!
proxy.rent();
}
}
可以看见运行代码之后,都是代理的操作
案例演示
比如我们想做一个业务类的功能代码,需要增删改查,后面突然新增了一个功能,是需要日志的,记录增删改查的信息。那么我们肯定是不能再原来的业务代码进行修改了,因为不精简,不眼睛
定义接口代码
- 这里定义了四个抽象方法
package Spring_Proxy.Demo02;
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
实现接口类代码
- 这里使用一个实现接口类来完成增删改查的操作
package Spring_Proxy.Demo02;
//实现接口类
public class UserServiceimpl implements UserService{
@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("查询一个用户");
}
//为什么不直接改实现类的代码?
//1.改动原有的业务代码,在公司中是大忌,如果代码崩了呢?
//2.业务是业务的方法,我们想添加其他的方法,我们用日志的方法
}
接口类代理代码
- 这里我们想实现一个新功能,每一次的操作都会有日志记录
- 我们要在接口代理的代码中实现,这是因为横向开发的原因,不能改变缘来实现接口业务的代码的完整性
- 缺点就是很麻烦,每一个方法都要添加log()
package Spring_Proxy.Demo02;
public class UserSerivceProxy implements UserService {
private UserServiceimpl userserivce;
public void setUserserivce(UserServiceimpl userserivce) {
this.userserivce = userserivce;
}
@Override
public void add() {
log("add");
userserivce.add();
}
@Override
public void delete() {
log("delete");
userserivce.delete();
}
@Override
public void update() {
log("update");
userserivce.update();
}
@Override
public void query() {
log("query");
userserivce.query();
}
//日志方法
public void log(String msg){
System.out.println("[Debug] 使用了"+msg+"方法");
}
}
启动类代码
- 启动类代码,这里有main方法
- 我们需要实现接口类代码,所以new实现接口类的一个对象
- 然后后面new了一个代理类的对象
- 最好使用代理类里面的方法
package Spring_Proxy.Demo02;
public class Client {
public static void main(String[] args) {
//代理类的一个对象 proxy
UserSerivceProxy proxy = new UserSerivceProxy();
//使用代理实现查询功能
proxy.query();
}
}
运行代码后可以发现,成功显示了
静态代理模式的好与坏
代理模式流程如图:讲究的是横向开发
好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共业务
- 公共也就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
缺点:
- 一个真实角色就会产生一个代理角色;代码会翻倍开发效率会变低
动态代理
动态代理的了解
- 动态代理和静态代理角色一样
- 动态代理的代理类是动态生成的,不是我们直接写好的!
- 动态代理分为两大类:基于接口的动态代理、基于类的动态代理
- 基于接口—JDK 动态代理【我们在这里使用】
- 基于类:cglib
- java字节码实现:javasist
需要了解两个类:
- Proxy:提供了创建动态代理类和实例的静态方法,
- InvocationHandler:调用处理程序实现的接口
为什么需要动态代理
因为静态代理,如果需要添加什么方法,一个个的很麻烦,动态代理的本质就是利用了反射机制。动态代理能够灵活轻松的减少代码量
代码的实现
动态代理类代码
这是一个万能的动态代理类,固定的写法,我们只需要知道:
- 有被代理的接口
- 生成得到的代理类
- 处理代理示例,并返回结果
- 总的来说就是我们需要替换target变量就好了
- 处理代理示例的时候,method方法invoke触发调用的是我们的变量target,args是固定的参数
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//等我们会用这个类,自动生成代理类!
public class ProxyInvocatioinHandler implements InvocationHandler{
//被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成得到代理类
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
//这里最后面的this,指的是InvocationHandler
}
@Override
//处理代理示例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//log("add"); 太笨了这个方法
log(method.getName());
//动态代理的本质,就是使用反射机制实现!
Object result = method.invoke(target, args);
return result;
}
//日志方法需求(业务需求)
public void log(String msg){
System.out.println("[Debug]执行力"+msg+"方法");
}
}
- 可以发现不用重复添加log()
接口启动类代码
- new一个接口实现类的对象 usersrrvice
- 然后new一个动态代理类的对象 pih
- 设置代理的对象为 代码实现类 usersrrvice
- 最后动态生成这个代理类,使用强制类型转换 为 proxy变量
package Spring_Proxy.Demo04;
import Spring_Proxy.Demo02.UserService;
import Spring_Proxy.Demo02.UserServiceimpl;
public class Client {
public static void main(String[] args) {
//真实角色
UserServiceimpl usersrrvice = new UserServiceimpl();
//代理角色,不存在
ProxyInvocatioinHandler pih = new ProxyInvocatioinHandler();
//设置要代理的对象
pih.setTarget(usersrrvice);
//动态生成的代理类
UserService proxy = (UserService) pih.getProxy();
proxy.add();
proxy.delete();
}
}
运行之后可以看见自动日志方法成功了,这就是动态代理,不需要静态代理的手动重复添加,增加代码量
动态代理的好处
- 可以使真实角色的操作更加纯粹!不用去关注一些公共业务
- 公共也就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要实现了同一个接口即可
反序列化中动态代理的应用
我们知道反序列化漏洞,需要的是一个入口类。
- 假设存在一个能够利用的类 为 B.f ,比如Runtime.exec
- 入口类为 A,最理想情况下是 A[O] -> o.f,那么我们传进去的参数 O 替换为 B即可,但是实际情况下很少
实际情况下:
- 入口类 A 存在 O.abc 这个方法,也就是 A[O] -> O.abc;如果O是一个动态代理类,O的 invoke方法存在 .f的方法,便可以漏洞利用了
A[O] -> O.abc
O[02] invoke -> 0.2f //此时将B 去替换为 O2
最后 --->
O[B] invoke -> b.f //达到漏洞利用效果
反序列化漏洞中的应用
- readObject->反序列化自动执行
- 而invoke->有调用函数
- 拼接两条链:任意->固定
- 在CC第一条链和jdk 7u21那两条链使用了动态代理。
属于这种类型:入口类参数中包含可控类,该类又调用其他危险方法的类,readObject时调用。