Java安全-RMI

前言

RMI 在 Java 里面还是很常用的,后续 Java 安全章节也会涉及到诸多 RMI 的问题,所以先学习一下有关RMI的相关部分

RMI概述

RMI全称为Remote Method Invocation(远程方法调用),是java编程语言中,一种实现远程过程调用的应用程序编程接口,存储于java.rmi包中,使用其方法调用对象时,必须实现Remote远程接口,能够让某个java虚拟机上的对象调用另外一个Java虚拟机中的对象上的方法

简单的来说,就是从一个java虚拟机调用本地或者其他计算机上的另一个java虚拟机中的方法,屏蔽了通信内容,且无需担心JVM中对象的不同(跨JVM操作)

RMI基本概念

从RMI设计角度来讲,基本分为三层架构模式来实现RMI,分别为RMI服务端,RMI客户端和RMI注册中心:

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

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

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

总体RMI的调用实现目的就是调用远程机器的类跟调用一个写在自己的本地的类一样,唯一区别就是RMI服务端提供的方法,被调用的时候该方法是执行在服务端

说简单一点就是,服务端那里有一个对象,客户端想要调用服务端里的那个对象的方法,而注册表就像一个中间人,服务端会将自己提供的服务的实现类交给这个中间人。并公开一个名称, 任何客户端都可以通过公开的名称找到这个实现类 , 并调用它

RMI的简单实现

定义一个远程接口

这个接口需要

  • 使用public声明,否则客户端在尝试加载实现远程接口的远程对象时会出错
  • 同时需要继承Remote接口
  • 接口的方法需要抛出java.rmi.RemoteException
  • 服务端实现这个远程接口
1
2
3
4
5
6
7
8
9
package RMI;

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

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

远程接口的实现类

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package RMI;

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

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

public class RemoteHelloWorld extends UnicastRemoteObject implements HelloInterface {

private static final long serialVersionUID = 1L;

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

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

RMI服务端

客户端通过这个URL直接访问远程对象,无需知道远程实例对象的名称

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package RMI;

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

// 服务端

public class RMIServer {
public static void main(String[] args) {
try {
HelloInterface h = new RemoteHelloWorld(); // 创建远程对象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();
}
}
}

RMI客户端配置

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package 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("lkf"));
}catch (MalformedURLException e) {
System.out.println("url格式异常");
} catch (RemoteException e) {
System.out.println("创建对象异常");
} catch (NotBoundException e) {
System.out.println("对象未绑定");
}
}
}