Call on extensible RMI

An introduction to JERI

The ability to invoke methods on one Java object from objects residing in another JVM has been a standard Java feature since the JDK 1.1 release. The Remote Method Invocation (RMI) framework makes a remote method invocation follow a local method call's syntax, while also explicitly acknowledging the semantic differences between local and remote object invocation. The combination of that simplicity and power has made RMI a popular distributed programming model.

Its popularity notwithstanding, RMI thus far has been limited to distributed systems that operate within the confines of a single administrative domain. The main reason for that limit: RMI lacked the security features required of wide-area distributed systems—especially systems that perform method invocations across the public Internet. Access to RMI calls' customization features was not part of the standard RMI API, and existing RMI implementations did not provide programmatic access to securing RMI method invocations.

In addition, growing experience with RMI provided a wealth of new feature requests from RMI users. One common developer gripe was the need to explicitly generate stub classes for an RMI server with the rmic compiler instead of dynamically creating proxy classes.

The Sun Microsystems RMI team answered the need for a customization API and many RMI feature requests in the latest Java RMI release, Jini Extensible Remote Invocation (JERI). JERI provides programmatic access to each layer of an RMI call via an API and allows an RMI service deployer to choose the RMI implementation most suitable in a deployment scenario. JERI also defines a uniform mechanism to make remote objects available to answer remote method calls (object exporting), which was not standardized in prior RMI releases. The result of the new features is that RMI calls can now adhere to any security requirement. While distributed system security is not a novel subject, securing RMI calls poses a few special challenges due to RMI's mobile code capability.

The case for secure mobile code

When RMI debuted, Web-scale remote method invocation was hardly a feature required of distributed systems. At that time, Web-based distributed computing amounted to simple interaction between Web browsers and Web servers in the form of CGI programs and HTTP servlets. As Web-aware distributed systems' requirements grew over the last several years, the confines of HTTP POST and GET commands for distributed computing became apparent. In response, Simple Object Access Protocol (SOAP) emerged as an XML-based messaging layer on top of HTTP and other Internet protocols. XML-based Web services rely on the exchange of SOAP messages to facilitate distributed computing.

From a Java programmer's viewpoint, SOAP and Java couldn't be more different. SOAP defines a way to encode textual and binary data in XML data structures; Java code, on the other hand, can express not only data, but also algorithms that operate on data. While SOAP-aware APIs, such as SOAP with Attachments API for Java (SAAJ) and Java API for XML-based RPC (JAX-RPC), can make a SOAP-based method invocation syntactically appear as if that invocation was a simple Java method call, no SOAP-aware API can mask the semantic difference between Java and SOAP.

When a SOAP message traverses the network, that message is confined to carrying markup tags that might convey some semantic meaning at a distant network location. For SOAP to work, that distant network node must first be able to interpret the semantic markup contained in a SOAP message and, second, must have the available code to operate on the SOAP message data. Interpreting data elements and performing a set of agreed-upon operations on that data form the core of a SOAP-based Web service protocol. Current XML-based Web services define SOAP protocols for specialized problem domains such as Web service registries (Universal Description Discovery and Integration (UDDI) and ebXML) or electronic data interchange (EDI).

In contrast to SOAP, RMI relies on Java code as a unit of information exchange in a distributed system. As a programming language, Java encapsulates both data and code. That difference allows RMI system participants to share only the semantics of objects expressed in Java code. The protocols each object uses for network communication remains an implementation detail hidden from other network participants. That single requirement, in turn, raises the abstraction level of a distributed system from protocols to interactions between objects. That higher abstraction level lends network programming an object-oriented flavor and affords a close semantic match between local and network programming.

RMI heralded that simpler programming model by solving a crucial problem: automatically moving code on the network. To illustrate that achievement's importance, consider the following two Java classes:

public interface Worker {
     public Result processJob(Job job);
}
public interface Job {
     public Object compute();
}

The first interface defines a worker in a generic compute server. When a Job instance is passed to Worker's processJob() method, Worker invokes the Job object's compute() method and returns the results to the caller. Figure 1 illustrates that generic compute server.

Figure 1. A generic compute server

If Worker's caller resides in the same JVM as the Worker, the caller can assume that the code for the parameter Job is available to the Worker object as well. In Java terms, the caller can assume the JVM that loaded the classes for Worker can also load Job's classes.

That assumption may no longer hold when Worker and its caller reside in different JVMs, possibly on different hosts. Since Job is a parameter in Worker's single method, the code for the Job interface must be available locally to Worker's JVM. However, code for a specific Job implementation might not be available locally to Worker. For example, a Job might be implemented as follows:

public class LongComputeJob implements Job {
     private ParamOne firstParam;
     private ParamTwo secondParam;
     public LongComputeJob(ParamOne firstParam, ParamTwo secondParam) {
          this.firstParam = firstParam;
          this.secondParam = secondParam;
     }
    
     public Object compute() {
         return CPUIntensiveAlgorithm.crunch(firstParam, secondParam);
     }
}

That Job implementation defines four additional classes: LongComputeJob.class, ParamOne.class, ParamTwo.class, and CPUIntensiveAlgorithm.class. All four classes must be loaded in the JVM that executes Worker. The Java RMI runtime ensures that those classes automatically download as-needed from the network, if they are not already available on Worker's host. (See "Object Mobility in the Jini Environment" (JavaWorld, January 2001) for details of RMI's mobile code feature.) Figure 2 illustrates an RMI system based on mobile code.

Figure 2. Dynamic RMI code downloading

In most RMI implementations, code downloading and code execution occur without the possibility to programmatically intervene in these operations. That convenience plants the seeds of many dangers. Since a Worker has no way to fathom how a Job is implemented before it starts executing a newly downloaded Job, it has no way to control that job's execution. Specifically, Worker cannot intercept a Job before invoking its methods or restrict access to the processJob() method based on the invoker's identity.

The lack of an RMI server's configuration possibilities leads either to ad-hoc solutions or an all-or-nothing security situation. In the latter case, if CPUIntensiveAlgorithm is implemented such that it instructs Worker to reformat the local hard disk, once Worker possesses sufficient privileges in its JVM, Worker's JVM will happily oblige in that act of malice. Because most RMI implementations do not provide a way to programmatically inspect and trace a remote method call, there is no way to grant different permissions based on the caller's identity or the type of Job submitted. All of Worker's privileges are, in effect, given to any client invoking Worker.

That simplistic security model renders RMI unsuitable for Web-wide distributed computing where a system is open to potentially any client invocation. JERI alleviates these problems by giving API-level access to control all aspects of an RMI call. The new JERI APIs do not replace the existing RMI API. Rather, they complement it with a configuration layer to which developers can explicitly program. Because that configuration layer lets a developer extend and tailor every detail of an RMI call, the new RMI implementation is named Extensible Remote Method Invocation.

Sun developers first proposed their Extensible RMI to the Java Community Process (JCP) in the form of two Java Specification Requests (JSRs) in 2001. Due to the exigencies of JCP politics, the JCP Executive Committees did not approve those JSRs. As a result, Extensible RMI, regrettably, has not become a part of the Java 2 Platform, Standard Edition (J2SE). Instead, the Sun team decided to continue the work on Extensible RMI as the foundation of the Davis Jini community project. In 2002, the Jini community voted to make the Davis specifications official Jini standards. Sun's Jini Starter Kit 2.0, released in June 2003, provides a full implementation of Extensible RMI, as well as core Jini services that use Extensible RMI. Based on Extensible RMI, the Jini 2.0 release features a fully customizable security framework.

Because of its initial release as the foundation of secure Jini, Extensible RMI is now called Jini Extensible Remote Method Invocation, or JERI. In the future, Extensible RMI will likely find its way into other RMI implementations unrelated to the Jini Starter Kit.

Client- and server-side RMI programming models

The mobile code semantics described in the previous section only form part of the RMI programming model. The RMI programming model's client-side portion, in addition to mobile code, calls for remote objects to implement the java.rmi.Remote interface, and its remotely invocable methods to declare java.rmi.RemoteException. The RMI runtime serializes a remote method call's parameter values and annotates the codebase where those parameter values' classes were loaded from on that serialized object stream as a list of URLs. When a Remote object is passed as a parameter to an RMI call, the RMI runtime substitutes a special stub for that object. When transferred across the network, that stub forwards method invocations to the remote object.

For an object to accept method invocations arriving from the network, that object must be exported by the RMI runtime. Exporting a Remote object forms the core of the server-side RMI programming model. While the initial RMI specifications clearly defined the client's programming model, they left considerable leeway about the server side. As a result, various RMI implementations export objects differently. For instance, if an object extends UnicastRemoteObject, the RMI runtime in the JDK will automatically export that object. How that exporting occurs, however, is beyond a developer's control and is specific to an RMI implementation. By the same token, how an object is unexported depends on an RMI implementation as well.

A key JERI contribution is a new server-side programming model that defines a standard way to export and unexport an object. Rather than having the programmer hard-code the specifics of exporting and unexporting an object, the JERI server-side programming model relies on deployment-time configuration options that configure an Exporter instance for each remote object. When configuring an object's Exporter, the configuration options can specify any restrictions, or constraints, to be placed on the exported object. Many of those constraints may be security related: For instance, a constraint might require that a client authenticate itself before invoking the server's remotely accessible methods.

Configuring an exporter

An object that implements the net.jini.config.Configuration interface represents an application's configuration. For example, in the case of the RMI compute server, a Configuration would control how an exporter exports a worker. Instead of directly instantiating a Configuration, an application obtains its configuration from a ConfigurationProvider. A configuration provider can retrieve an application's configuration in any manner. The default configuration provider for Jini and JERI applications relies on a ConfigurationFile class, which, in turn, populates the configuration data based on the contents of a configuration file.

A configuration consists of configuration entries. Each entry is identified by the component that the entry configures and the entry's name. When obtaining a configuration entry from a configuration, optionally a default value can be specified as well. The following code snippet shows how to obtain a configuration for the compute server's worker, how to use that configuration to retrieve an exporter, and, finally, how to export the worker with that exporter:

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more