Java RMI 学习笔记

最近想学习一下Java相关的安全知识,就找了几篇文章来学习下,从Java RMI开始吧

什么是 JAVA RMI ?

RMI ( Remote Method Invocation , 远程方法调用 ) 能够让在某个 Java虚拟机 上的对象像调用本地对象一样调用另一个 Java虚拟机 中的对象上的方法 , 这两个 Java虚拟机 可以是运行在同一台计算机上的不同进程, 也可以是运行在网络中不同的计算机上 .

RMI分为三个主体部分:

Client-客户端:客户端调用服务端的方法

Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果。

Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用。

JAVA RMI 的流程 .

如果让客户端直接访问服务端的资源 , 那么有可能出现越权访问的风险 . 在 JAVA RMI 中 , 通过一个 中间人 来解决这类问题 .

服务端( RMIServer ) 会将自己提供的服务的实现类交给这个中间人 , 并公开一个名称 . 任何客户端( RMIClient )都可以通过公开的名称找到这个实现类 , 并调用它 .

这样以来 , 不仅避免了客户端和服务端资源的直接交互 . 也使得客户端能更好的查找要使用的对象( 直接去询问这个中间人 , 若中间人拥有对应实现类 , 那么客户端可以在本地直接调用该类的方法 . 若中间人没有对应的实现类 , 则说明服务端没有提供相应服务 )

这个中间人也被称为 RMIService / RMIRegister .

因此整个 RMI 的流程实际上分为三个部分 , RMIServer , RMIClient , RMIRegister . 其交互的流程如下所示 :

在这里插入图片描述

JAVA RMI 的简单例子

1.服务端编写一个远程接口

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

// 定义一个远程接口,继承java.rmi.Remote接口

public interface HelloInterface extends Remote {
    String Hello(String age) throws RemoteException;
}

既然 RMIServer 要提供服务 , 那么它一定会准备一个接口 , 让客户端通过这个接口来访问服务

需要说明的是 :

在 Java 中 , 如果一个类继承了 java.rmi.Remote 接口 , 那么该类将成为一个服务端的远程对象 , 供客户端访问并提供一定的服务 .

Remote 接口是一个标识接口 , 本身不包含任何方法 , 该接口用于标识其子类的方法可以被非本地的Java虚拟机调用

由于远程调用的本质依旧是 " 网络通信 " . 而网络通信是经常出现异常的 . 因此 , 继承 Remote 接口的接口的所有方法必须要抛出 RemoteException 异常 . 事实上 , RemoteException 也是继承于 IOException 的 .

2.要想调用远程接口 , 还需要一个实现类

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

// 远程接口实现类,继承UnicastRemoteObject类和Hello接口

public class HelloImp extends UnicastRemoteObject implements HelloInterface {

    private static final long serialVersionUID = 1L;

    protected HelloImp() throws RemoteException {
        super(); // 调用父类的构造函数
    }

    @Override
    public String Hello(String age) throws RemoteException {
        return "Hello" + age; // 改写Hello方法
    }
}

远程接口实现类必须继承UnicastRemoteObject类,用于生成 Stub(存根)和 Skeleton(骨架)。

Stub可以看作远程对象在本地的一个代理,囊括了远程对象的具体信息,客户端可以通过这个代理和服务端进行交互。

Skeleton可以看作为服务端的一个代理,用来处理Stub发送过来的请求,然后去调用客户端需要的请求方法,最终将方法执行结果返回给Stub。

其实 , 与其说是客户端和服务端进行交互 , 不如说是 客户端代理( Stub ) 和 服务端代理( Skeleton ) 在进行交互 .

同时跟进UnicastRemoteObject类源代码我们可以发现,其构造函数抛出了RemoteException异常。但这种写法是十分不好的,所以我们通过super()关键词调用父类的构造函数。

在这里插入图片描述

3.RMI服务器端

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

// 服务端

public class RMIServer {
    public static void main(String[] args) {
        try {
            HelloInterface h  = new HelloImp(); // 创建远程对象HelloImp对象实例
            LocateRegistry.createRegistry(1099); // 获取RMI服务注册器
            Naming.rebind("rmi://localhost:1099/hello",h); // 绑定远程对象HelloImp到RMI服务注册器
            System.out.println("RMIServer start successful");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

LocateRegistry.createRegistry(1099); : 即在本地创建并启动 RMIService , 被创建的 RMIService 服务将会在指定的端口上监听请求 .

RMIService ( RMIRegister ) 服务的默认端口为 : 1099

java.rmi.Naming 类提供在对象注册表中存储和获得远程对远程对象引用的方法 . 这里将远程对象 " h " 绑定到 rmi://localhost:1099/hello 这个 URL 上 . 客户端可以通过这个 URL 直接访问远程对象 .

这里涉及到了另一个问题 : 即 " 开发人员不知道远程实例对象的名称是什么 ." 而通过这种绑定机制 , 开发人员仅需要知道一个公开的路径(URL) , 就可以直接访问到对应的远程对象了 .

4.RMI客户端配置

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

// 客户端

public class RMIClient {
    public static void main(String[] args){
        try {
            HelloInterface h = (HelloInterface) Naming.lookup("rmi://localhost:1099/hello"); // 寻找RMI实例远程对象
            System.out.println(h.Hello("run......"));
        }catch (MalformedURLException e) {
            System.out.println("url格式异常");
        } catch (RemoteException e) {
            System.out.println("创建对象异常");
        } catch (NotBoundException e) {
            System.out.println("对象未绑定");
        }
    }
}

客户端只需要调用 java.rmi.Naming.lookup 函数,通过公开的路径从RMIService服务器上拿到对应接口的实现类, 之后通过本地接口即可调用远程对象的方法 .

因此 , 只需要一个接口 , 一个客户端连接程序 , 即可实现 JAVA 远程调用 .

5.运行结果

在这里插入图片描述
在这里插入图片描述
通过结果也可以知道,这个方法的调用,实际上也还是在服务端,而不是客户端。

JAVA RMI的一些攻击

因为在整个RMI机制过程中,都是进行反序列化传输,我们可以利用这个特性使用RMI机制来对RMI远程服务器进行反序列化攻击。

但实现RMI利用反序列化攻击,需要满足两个条件:

1、接收Object类型参数的远程方法

2、RMI的服务端存在执行pop利用链的jar包

后续的实例就不试了,因为这篇文章就是自己的一个入门学习,暂时了解了相关原理和知道反序列化传输就行,想查看实例以及相关的通信原理的,可以查看下面的参考文章

https://www.guildhab.top/2020/03/java-rmi-ldap-%e6%b5%81%e7%a8%8b%e5%88%86%e6%9e%90/

https://xz.aliyun.com/t/9261

https://xz.aliyun.com/t/6660#toc-5

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值