5.2.20
04/02/20
Last Modified 01/11/19 by Walter Tasin
tasin/RMITutorial2 Reload Page

RMITutorial (2) - Securitymanager und Stub

Dieses Dokument beruht auf Erkenntnissen, die in RMITutorial Teil 1 in Verbindung mit der JDK 1.5.0_06 getroffen wurden.
Bevor auf die eigentlichen Unterschiede aufmerksam gemacht wird, zuerst noch einige Beobachtungen:

Securitymanager

Es stellt sich die Frage, warum in der Beispielapplikation der Osterdatumsberechnung weder für die Server- noch für die Clientapplikation ein Securitymanager instantiiert werden muss und auch keine dazu passende .java.policy nötig ist?
Zur Erklärung hier ein Beispielaufruf des Servers, um die Funktion des Securitymanagers etwas besser zu verdeutlichen:
Es wird im Folgenden ein Securitymanager über den java-Aufruf explizit instantiiert:

C:\Java-Test>java -Djava.security.manager -Djava.rmi.server.codebase=file:///C:\Java-Test/ RMIDateServer
EXCEPTION beendet das Programm:
java.security.AccessControlException: access denied (java.net.SocketPermission 127.0.0.1:1099 connect,resolve)
        at java.security.AccessControlContext.checkPermission(Unknown Source)
        at java.security.AccessController.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkPermission(Unknown Source)
        at java.lang.SecurityManager.checkConnect(Unknown Source)
        at java.net.Socket.connect(Unknown Source)
        at java.net.Socket.connect(Unknown Source)
        at java.net.Socket.<init>(Unknown Source)
        at java.net.Socket.<init>(Unknown Source)
        at sun.rmi.transport.proxy.RMIDirectSocketFactory.createSocket(Unknown Source)
        at sun.rmi.transport.proxy.RMIMasterSocketFactory.createSocket(Unknown Source)
        at sun.rmi.transport.tcp.TCPEndpoint.newSocket(Unknown Source)
        at sun.rmi.transport.tcp.TCPChannel.createConnection(Unknown Source)
        at sun.rmi.transport.tcp.TCPChannel.newConnection(Unknown Source)
        at sun.rmi.server.UnicastRef.newCall(Unknown Source)
        at sun.rmi.registry.RegistryImpl_Stub.rebind(Unknown Source)
        at java.rmi.Naming.rebind(Unknown Source)
        at RMIDateServer.main(RMIDateServer.java:48)

Der oben aufgeführte Aufruf zeigt, dass erst durch die Instantiierung eines Securitymanagers überprüft wird, ob sich der Server mit der Registry auf Port 1099 verbinden darf.

  • Es wäre also erst jetzt eine .java.policy-Datei im Benutzerverzeichnis (z. B. /home/tasin (Linux) oder C:\Users\Tasin (Windows 7)) nötig,
    mit der der Server "sich selbst" erlaubt auf die Registry zugreifen zu dürfen.
  • Der Server übernimmt für das Binden an die Registry also die Aufgabe eines Clients, der
    sich an den Namensdienst "bindet".
  • Da die Registry hier auf dem gleichen Rechner wie die Serverapplikation läuft und kein Securitymanager für das Binden an eine lokale RMIRegistry nötig ist, kann man in diesem Fall auf den Securitymanager verzichten.
  • Entgegen der sich vielleicht aufdrängenden Meinung wird mit dem Securitymanager des Servers und der entsprechenden .java.policy NICHT geregelt wie ein anderer Rechner auf den Serverprozess zugreifen darf, sondern damit werden die Zugriffsrechte der Applikation (in diesem Fall des Servers) in seiner virtuellen Maschine geregelt.
  • Wie andere Rechner auf den Server zugreifen dürfen, werden durch andere Konzepte (z. B. Firewall, u. ä.) geregelt.

Mit der Verwendung der .java.policy werden also die Standardrechte der Applikation in der JVM erweitert, aber nur wenn ein Securitymanager für die Applikation instantiiert wird.

Mit der Instantiierung eines Securitymanagers und der Einrichtung der .java.policy werden also die Zugriffsrechte der Applikation ERWEITERT.

  • So erfordert das Laden von Code von entfernten Rechner (RMI class loader) in jedem Fall einen Securitymanager (auf dem Rechner, der das möchte <==> Client).

Zusammenfassung

Man kann also für die Beispielapplikation folgende Unterscheidungen treffen:

  • Standardrechte (ohne Securitymanager)
    • kein Nachladen von Code möglich
    • Binden mit der lokalen Registry erlaubt
    • Anfrage an eine lokale/entfernte Registry erlaubt
    • ...
  • Standardrechte mit Securitymanager (ohne .java.policy)
    • kein Nachladen von Code möglich
    • kein Binden mit der lokalen Registry erlaubt
    • ...
  • Erweiterte Rechte mit Securitymanager (mit geeigneter .java.policy)
    • Nachladen von Code möglich
    • Zugriff auf eine Registry erlaubt
    • ...

Solange die Server- oder Clientapplikation keinen Code von einem entfernten Rechner nachladen soll, ist kein Securitymanager nötig.

Stub-Klassen-Erzeugung und -Verwendung

  • Seit JDK/JRE-Version 1.5 ist die Erzeugung der Stub-Klasse der Serverimplementation nicht mehr nötig.
  • Die Server-Stub-Klasse muss nicht mehr explizit zur Verfügung stehen. An ihrer Stelle kann auch eine dynamisch generierte Klasse (s. a. RMITutorial - Aufruf des Servers) stehen.
    Ganz im Gegenteil!
    Sollte trotzdem eine Stub-Klasse mit rmic erzeugt worden sein und diese der Serverapplikation beim Start zur Verfügung stehen (CLASSPATH oder Codebase), versucht auch der Client (und die Registry) diese Klassendatei zu erhalten.
    (VERSIONSINTEROPERABILITÄT!!).
    • Dazu wird zuerst der CLASSPATH durchsucht und danach die - durch den Serveraufruf - übergegebene Codebase.
    • Damit der Client aber entfernten (über die Codebase erreichbare) Klassendateien nachladen kann, muss ein Securitymanager instantiiert werden (s. oben).

Wann ist eine explizite Stub-Klasse nötig?

Sollte das Projekt mit einer Java-Version < 1.5 kompiliert werden
oder
erlaubt werden, dass ein Client (Java-Version < 1.5) mit einem Server (>= 1.5) interoperieren kann,

so muss man die Stub-Klasse explizit erzeugen und dem Client geeignet zur Verfügung stellen.

Für diesen Fall muss man folgendes beachten:

  • sollte Server und Client auf unterschiedlichen Rechnern laufen, dann sollte die Stub-Klasse über die Codebase erreichbar sein.
  • existiert eine - für die Serverapplikation erreichbare - Stub-Klasse, so muss auch ein Client, der mit Java-Version >=1.5 kompiliert wurde einen Securitymanager instantiieren, um diese über die Codebase nachladen zu können.
  • bei Instantiierung eines Securitymanagers wird auch eine geeignete .java.policy benötigt.

Beispielaufrufe für unterschiedliche Rechner

SERVERSEITIG (server.example.com)

  • Stuberzeugung

C:\Java-Test>rmic RMIDateServer

C:\Java-Test>

  • Publikation des Stubs (und des RMI-Interfaces) in ein über http:// erreichbares Verzeichnis

C:\Java-Test>copy RMIDateServer_Stub.class D:\Inet_pub\eastercalc

C:\Java-Test>rem Die Datei RMIDateServer_Stub.class kann in diesem Verzeichnis auch geloescht werden.
C:\Java-Test>copy RMIDateCalc.class D:\Inet_pub\eastercalc

C:\Java-Test>

  • Start der Registry und der Serverapplikation

C:\ganzwoanders> rem Die CLASSPATH-Variable soll hier mal nicht auf C:\Java-Test zeigen

C:\ganzwoanders>start rmiregistry

C:\ganzwoanders>cd \Java-Test

C:\Java-Test>java -Djava.rmi.server.codebase=http://server.example.com/eastercalc/ RMIDateServer
RMIServer gestartet mit Algorithmus 'EasterCalculator'

CLIENTSEITIG (client.example.com)

  • Anlegen einer geeigneten .java.policy-Datei im Heimatverzeichnis des Benutzers

grant
{
  permission java.net.SocketPermission
    "*:1024-65535", "connect,accept";
  permission java.net.SocketPermission 
    "*:80", "connect";
};

  • Starten des Clients mit Securitymanager

C:\Client-Test>java -Djava.security.manager RMIDateClient 2343 server.example.com
Resultat: 11.4.2343

C:\Client-Test>

damit der Benutzer den Securitymanager nicht über die Kommandozeile instantiieren muss, kann diese in der Clientapplikation durchgeführt werden.
Die Clientapplikation muss dazu wie folgt erweitert werden:

/*
 * RMIDateClient.java
 *
 * Created on 21. Januar 2006
 */
import java.rmi.*;

/**
 * RMIClient to calculate a certain date
 * @author Tasin
 */
public class RMIDateClient 
{
   public static void main(String args[])
   {
       if (args.length==0)
       {
           System.out.println("Aufruf:\n"+
                   "RMIDateClient Jahr [<Serveradresse>]");
           System.exit(1);
       }
       int year=Integer.parseInt(args[0]);
       String serverURL="rmi://"+
               ((args.length>1) ? args[1] : "localhost") + "/EasterCalculator";
       
       try
       {
         /* 
            HIER wird der Securitymanager instantiiert
            (falls nicht schon geschehen!!)
         */
         if (System.getSecurityManager()==null)
         {
           System.setSecurityManager(new SecurityManager());
         }
         // Ende des zuletzt eingefuegten Codes
         RMIDateCalc dcalc=(RMIDateCalc) Naming.lookup(serverURL);
         System.out.println("Resultat: "+dcalc.RMICalculate(year));
       }
       catch (Exception ex)
       {
         System.out.println("EXCEPTION beendet das Programm:");
         ex.printStackTrace();
       }
   }
}

Nachteil: Sobald dieser Code verwendet wird, muss auf alle Fälle eine .java.policy installiert sein.
Sollte auch der Zugriff zu Dateien ermöglicht werden, dann muss die .java.policy entsprechend ergänzt werden:
Z. B.:

grant
{
  permission java.net.SocketPermission
    "*:1024-65535", "connect,accept";
  permission java.net.SocketPermission 
    "*:80", "connect";
  permission java.io.FilePermission
    "${user.home}${/}-", "read";
  permission java.io.FilePermission
    "L:${/}-", "read";
};

Diese Ergänzngen ermöglichen der Applikation lesenden Zugriff auf die Dateien und Unterverzeichnisse des Benutzerverzeichnisses (C:\Dokumente und Einstellungen\Benutzername) und auf das gesamte Laufwerk L:.

Für die nachfolgenden Dokumente wird der Einfachheit halber vorausgesetzt, dass die Beispiele nur für Java-Versionen >= 1.5 lauffähig sein sollen.
Im nächsten Teil des Tutorials "RMITutorial Teil 3" wird nun die Serverapplikation erweitert.