1.什么是rmi?
RMI(Remote Method Invocation)即远程方法调用,是分布式编程中的一个基本思想。实现远程方法调用的技术有很多,比如CORBA、WebService,这两种都是独立于各个编程语言的。 而Java RMI是专为Java环境设计的远程方法调用机制,是一种用于实现远程调用(RPC,Remote Procedure Call)的Java API,能直接传输序列化后的Java对象和分布式垃圾收集。它的实现依赖于JVM,因此它支持从一个JVM到另一个JVM的调用。 在Java RMI中,远程服务器实现具体的Java方法并提供接口,客户端本地仅需根据接口类的定义,提供相应的参数即可调用远程方法,其中对象是通过序列化方式进行编码传输的。所以平时说的反序列化漏洞的利用经常是涉及到RMI,就是这个意思。 RMI依赖的通信协议为JRMP(Java Remote Message Protocol,Java远程消息交换协议),该协议是为Java定制的,要求服务端与客户端都必须是Java编写的。
交互过程
交互过程可简单概述为:
- 首先,启动RMI Registry服务,启动时可以指定服务监听的端口,也可以使用默认的端口(1099);
- 其次,Server端在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming/Context/Registry等类的bind或rebind方法将刚才实例化好的实现类注册到RMI Registry上并对外暴露一个名称;
- 最后,Client端通过本地的接口和一个已知的名称(即RMI Registry暴露出的名称),使用RMI提供的Naming/Context/Registry等类的lookup方法从RMI Service那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,便可以实现远程调用对象的方法了;
2.代码工程
实验目标
实验一个简单rmi服务,并且通过客户端调用它
rmi-server
这是一个服务端工程,主要提供rmi service接口
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rmi</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rmi-server</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.et</groupId>
<artifactId>rmi-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.200</version>
</dependency>
</dependencies>
</project>
config
package com.et.rmi.server.config;
import com.et.rmi.server.dao.CustomerRepository;
import com.et.rmi.server.model.Customer;
import com.et.rmi.server.service.CustomerServiceImpl;
import om.et.rmi.common.CustomerService;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.stereotype.Component;
import java.util.logging.Logger;
@Component
public class RmiServerApplicationRunner implements ApplicationRunner {
private CustomerRepository repository;
private CustomerServiceImpl customerService;
private final Logger log = Logger.getLogger(this.getClass().getName());
public RmiServerApplicationRunner(CustomerServiceImpl customerService) {
this.customerService = customerService;
}
@Override
public void run(ApplicationArguments args) throws Exception {
Customer customer1 = new Customer("John", "Smith", "123-456-7890");
customerService.saveCustomer(customer1);
customerService.getCustomers().forEach(System.out::println);
}
@Bean
public RmiServiceExporter customerServiceExporter() {
RmiServiceExporter customerServiceExporter = new RmiServiceExporter();
customerServiceExporter.setRegistryPort(1199);
customerServiceExporter.setServiceName("customerService");
customerServiceExporter.setServiceInterface(CustomerService.class);
customerServiceExporter.setService(customerService);
log.info("Started RMI Server");
return customerServiceExporter;
}
}
service
package com.et.rmi.server.service;
import com.et.rmi.server.dao.CustomerRepository;
import com.et.rmi.server.mapper.CustomerMapper;
import com.et.rmi.server.model.Customer;
import om.et.rmi.common.CustomerDTO;
import om.et.rmi.common.CustomerService;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class CustomerServiceImpl implements CustomerService {
private CustomerRepository repository;
public CustomerServiceImpl(CustomerRepository repository) {
this.repository = repository;
}
@Override
public CustomerDTO getCustomer(long id) {
Customer customer = repository.findById(id).orElseThrow(IllegalArgumentException::new);
CustomerMapper mapper = new CustomerMapper();
CustomerDTO dto = mapper.mapToDTO(customer);
System.out.println(dto);
return dto;
}
public List<Customer> getCustomers() {
return (List<Customer>)repository.findAll();
}
public void saveCustomer(Customer customer) {
repository.save(customer);
}
}
dao
package com.et.rmi.server.dao;
import com.et.rmi.server.model.Customer;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface CustomerRepository extends CrudRepository<Customer, Long> {
Optional<Customer> findById(long id);
}
mapper
package com.et.rmi.server.mapper;
import com.et.rmi.server.model.Customer;
import om.et.rmi.common.CustomerDTO;
public class CustomerMapper {
public CustomerMapper() {
}
public CustomerDTO mapToDTO(Customer customer){
CustomerDTO dto = new CustomerDTO();
dto.setFirstName(customer.getFirstName());
dto.setLastName(customer.getLastName());
dto.setSocialSecurityCode(customer.getSocialSecurityCode());
return dto;
}
}
model
package com.et.rmi.server.model;
import javax.persistence.*;
@Entity
@SequenceGenerator(name = "CUST_SEQ", initialValue = 1_000_001)
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "CUST_SEQ")
private long id;
private String firstName;
private String lastName;
private String socialSecurityCode;
public Customer() {
}
public Customer(String firstName, String lastName, String socialSecurityCode) {
this.firstName = firstName;
this.lastName = lastName;
this.socialSecurityCode = socialSecurityCode;
}
public long getId() {
return id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getSocialSecurityCode() {
return socialSecurityCode;
}
public void setSocialSecurityCode(String socialSecurityCode) {
this.socialSecurityCode = socialSecurityCode;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", socialSecurityCode='" + socialSecurityCode + '\'' +
'}';
}
}
application.properties
spring.jpa.show-sql=false spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
rmi-client
这是一个客户端工程,主要调用远程的rmi服务
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>rmi</artifactId>
<groupId>com.et</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>rmi-cilent</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.et</groupId>
<artifactId>rmi-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
controller
package com.et.rmi.client.controller;
import om.et.rmi.common.CustomerDTO;
import om.et.rmi.common.CustomerService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
@Controller
@RequestMapping(value = "customers")
public class CustomerController {
private RmiProxyFactoryBean proxyFactoryBean;
public CustomerController(RmiProxyFactoryBean proxyFactoryBean) {
this.proxyFactoryBean = proxyFactoryBean;
}
@RequestMapping(value = "{id}", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<CustomerDTO> getCustomer(@PathVariable long id) {
CustomerService service = (CustomerService) proxyFactoryBean.getObject();
CustomerDTO dto = service.getCustomer(id);
return ResponseEntity.ok(dto);
}
}
config
package com.et.rmi.client.config;
import om.et.rmi.common.CustomerService;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.logging.Logger;
@Component
public class Config {
public final Logger log = Logger.getLogger(this.getClass().getName());
@Bean
public RmiProxyFactoryBean proxyFactoryBean() {
String remoteHost = System.getProperty("RMI_SERVER_HOST");
if(StringUtils.isEmpty(remoteHost)){
remoteHost="127.0.0.1";
}
String rmiHost = String.format("rmi://%s:1199/customerService", remoteHost);
log.info("RMI Host name is " + rmiHost);
RmiProxyFactoryBean proxy = new RmiProxyFactoryBean();
proxy.setServiceInterface(CustomerService.class);
proxy.setServiceUrl(rmiHost);
proxy.afterPropertiesSet();
return proxy;
}
}
application.properties
server.port=8081
rmi-common
这是一个公共包,server和client都要引用
package om.et.rmi.common;
import java.io.Serializable;
public class CustomerDTO implements Serializable {
private String firstName;
private String lastName;
private String socialSecurityCode;
public CustomerDTO() {
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getSocialSecurityCode() {
return socialSecurityCode;
}
public void setSocialSecurityCode(String socialSecurityCode) {
this.socialSecurityCode = socialSecurityCode;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("CustomerDTO{");
sb.append("firstName='").append(firstName).append('\'');
sb.append(", lastName='").append(lastName).append('\'');
sb.append(", socialSecurityCode='").append(socialSecurityCode).append('\'');
sb.append('}');
return sb.toString();
}
}
package om.et.rmi.common;
public interface CustomerService {
CustomerDTO getCustomer(long id);
}
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
- GitHub - Harries/springboot-demo: a simple springboot demo with some components for example: redis,solr,rockmq and so on.
3.测试
- 启动rmi-server服务
- 启动rmi-client服务
- 访问http://127.0.0.1:8081/customers/1000001
- 返回
{"firstName":"John","lastName":"Smith","socialSecurityCode":"123-456-7890"}
4.引用
- https://www.tutorialspoint.com/java_rmi/java_rmi_introduction.htm
- https://docs.oracle.com/javase/8/docs/technotes/guides/rmi/index.html
- Spring Boot集成rmi快速入门demo | Harries Blog™