JAVA序列化之RMI远程调用

RMI(Remote Method Invocation)是Java中的远程过程调用(Remote Procedure Call,RPC)实现,是一种分布式Java应用的实现方式。它的目的在于对开发人员屏蔽横跨不同JVM和网络连接等细节,使得分布在不同JVM上的对象像是存在于一个统一的JVM中一样,可以很方便的互相通讯。通讯就涉及到了数据的编码和解码,对于一般的数据类型我们不需要这么做,但是涉及到比较复杂的数据类型,例如对象,RMI利用到了序列化,使得数据的编码与解码对于开发人员透明起来,我们不需要关注数据如何传输,只需要实现相关方法就能做到远程调用。

一、序列化

对象的序列化主要是使用JAVA中的ObjectOutputStream的writeObject(Object obj)来序列化和ObjectInputStream的readObject()来反序列化。
1、在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。当然目前大多数项目存储会员状态信息都会用到Memcached。
2、当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。此处RMI就利用了这点来帮我们编排对象数据信息。

总结就有如下优点:

  • 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中。
  • 便于在网络上传送对象的字节序列。
package rmiTest;

import java.io.Serializable;

public class Person implements Serializable{

    private static final long serialVersionUID = -6379887343250921447L;

    private int id;
    private String name;
    private String sex;

    public Person(){}

    public Person(int id, String name, String sex) {
        super();
        this.id = id;
        this.name = name;
        this.sex = sex;
    }
    //getXx();
    //setXx();
}
package serializeTest;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import rmiTest.Person;

public class TestObjSerializeAndDeserialize {

    public static void main(String[] args) {
        Person person = new Person(1, "丁烁", "man");
        try {
            serialize(person, "E:/ds.txt");
            deserialize("E:/ds.txt");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //序列化
    public static void serialize(Object object, String path) throws Exception{
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(path));
        outputStream.writeObject(object);
        outputStream.flush();
        outputStream.close();
        System.out.println("序列化成功!");
    }
    //反序列化
    public static void deserialize(String path) throws Exception{
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(path));
        Person person =(Person) inputStream.readObject();
        inputStream.close();
        System.out.println("反序列化成功!");
        System.out.println("id:"+person.getId());
        System.out.println("name:"+person.getName());
        System.out.println("sex:"+person.getSex());
    }

}

输出结果:
结果输出
文件生成:
生成的文件

注意事项:实体类上必须实现序列化接口,并且设定一个serialVersionUID。如果没有添加该字段,序列化对象之后,修改实体类再进行反序列化就会报错。java编译器会自动给这个class进行一个摘要算法来生成serialVersionUID,只要这个文件多一个空格,得到的serialVersionUID就会截然不同。所以修改之后java编译器又给我们生成了一个serialVersionUID,反序列化时两个版本号不一致就会报错。

二、jar命令

jar(java archive file),这里为了更加真实的模拟远程接口调用,我们使用一个简单的面向接口编程,包含一个客户端和一个服务端。服务端包含了接口、实现以及实体类,客户端引入接口以及实体类的jar包,通过远程接口调用来调用接口的实现类。所以简要的梳理一下如何使用jar命令来进行打包相关类文件。当然在实际的项目中会使用maven来构建jar工程。

举例:
jar cvf d:\rmitest.jar D:\workspace\tjfae-v2300-20160410\appTest\bin\rmiTest
该命令会将rmiTest下的所有文件打包成jar并命名为rmitest.jar存放与d盘下。当然也可以指定多个目录、多个类文件。但是这样有一个弊端,我们打出的jar并不是按照我们所想的package目录打出来的。我们反编译一个jar如下图:
这里写图片描述
jar里面的类文件路径是按照我们打包时的路径所写,这样会导致我们在引用这个jar包时,报路径错误。明白了这点,我们可以将dos下的当前目录调整到与package对应位置,再执行打包命令。先将dos路径调整到D:\workspace\tjfae-v2300-20160410\appTest\bin下,然后执行如下命令:
jar cvf d:\rmitest.jar rmiTest
这里写图片描述

此时就已经打出了我们想要的jar包,为后面的远程接口调用做准备。

三、RMI远程调用实例

前面介绍了序列化和如何利用jar命令打包,下面的例子分为客户端和服务器端,服务器端包含了接口和接口的实现,并且将实现注册到一个服务地址上。然后用jar命令将服务器端的接口和实体类打包,在客户端引用,这样客户端只有接口,而不会看到具体实现。这里为了方便,将接口和实现放在同一个工程。但是更好方式是:

  1. 将接口写在一个工程A;
  2. 将工程A打成jar包;
  3. 在工程B中引入工程A的jar包,实现这些接口并注册服务,工程B即服务端;
  4. 在工程C中引入工程A的jar包,获取服务并注入给相应接口,工程C即客户端;
  5. 客户端远程调用。

本例中涉及到的类文件如下:
服务端:
Person:实体类,实现Serializable进行序列化;
PersonService:服务接口,继承Remote;
PersonServiceImp:服务实现,继承UnicastRemoteObject,并且实现PersonService;
ServerTest:注册PersonService这个服务;
客户端:
引入事先打好的jar包(Person.class、PersonService.class)
ClientTest:调用远程服务。

Person上面已经贴出。
PersonService包含一个getPerson的抽象方法。

package rmiTest;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface PersonService extends Remote {

    public Person getPerson() throws RemoteException;

}

PersonServiceImp必须要继承UnicastRemoteObject,如果不继承这个类,那么客户端每次lookup出来的都是不一样的对象,可以通过在服务端添加一个成员变量,每调用一次自增一下来进行测试,这里不做多余讲解。

package rmiTest;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class PersonServiceImp extends UnicastRemoteObject implements PersonService {

    private static final long serialVersionUID = 1L;

    protected PersonServiceImp() throws RemoteException {
        super();
    }

    @Override
    public Person getPerson() throws RemoteException {
        Person person = new Person();
        person.setId(1);
        person.setName("ds");
        person.setSex("M");
        return person;
    }

}

ServerTest服务端注册这个服务

package rmiTest;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class ServerTest {

    public static void main(String[] arg){
        try {
            PersonService personService = new PersonServiceImp();
            LocateRegistry.createRegistry(6600);
            Naming.bind("rmi://127.0.0.1:6600/PersonService", personService);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

ClientTest客户端进行远程调用,客户端要先引入事先打好的jar包


import java.rmi.Naming;

import rmiTest.Person;
import rmiTest.PersonService;

public class ClientTest {

    public static void main(String[] args) {
        try {
            PersonService personService = (PersonService) Naming.lookup("rmi://127.0.0.1:6600/PersonService");
            Person person = personService.getPerson();
            System.out.println("ID:"+person.getId());
            System.out.println("Name:"+person.getName());
            System.out.println("Sex:"+person.getSex());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我知道你的问题了。针对你提到的这个问题,我可以给你一些解释和分析。 首先,RMI(Remote Method Invocation)是Java远程调用机制的一种实现方式,它允许在不同的JVM进程之进行Java对象的传输和远程调用。而反序列化(Deserialization)是将二进制数据转换为Java对象的过程。在Java中,对象的序列化和反序列化是通过ObjectInputStream和ObjectOutputStream类来实现的。 RMI远程反序列化RCE漏洞是指,攻击者可以通过构造恶意的序列化数据,使得服务端在反序列化时执行恶意代码,从而导致远程代码执行漏洞。这个漏洞的根本原因在于Java序列化机制的设计缺陷,攻击者可以通过构造恶意的序列化数据来绕过Java的安全机制,从而执行任意代码。 而Spring框架的反序列化远程代码执行漏洞,则是指攻击者可以通过构造恶意的序列化数据,使得Spring框架在反序列化时执行恶意代码,从而导致远程代码执行漏洞。这个漏洞的影响范围非常广泛,涵盖了Spring框架的多个版本,包括Spring MVC、Spring WebFlow、Spring Data等。 总的来说,RMI远程反序列化RCE漏洞和Spring框架的反序列化远程代码执行漏洞都是Java序列化机制的设计缺陷所导致的安全漏洞。攻击者可以通过构造恶意的序列化数据来绕过Java的安全机制,从而执行任意代码。因此,在开发Java应用程序时,需要注意对序列化和反序列化数据的处理,避免出现安全漏洞。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值