vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 16

Ausnahmezustände: Fehlerbehandlung und Sicherheit

Programmierer aller Sprachen bemühen sich, fehlerfreie Programme zu schreiben, Programme, die nie abstürzen, Programme, die jede Situation mit Eleganz behandeln können und ungewöhnliche Situationen in den Griff bekommen, ohne daß der Benutzer eingreifen müßte. Gute Vorsätze hin und her - solche Programme gibt es nicht.

In realen Programmen treten Fehler auf, weil entweder der Programmierer nicht an jede Situation gedacht hat, in die das Programm kommen kann (oder er hatte nicht die Zeit, das Programm ausgiebig genug zu testen), oder weil Situationen auftreten, die sich der Kontrolle des Programmierers entziehen - schlechte Daten vom Benutzer, beschädigte Dateien, die nicht die richtigen Daten beinhalten, Geräte, die nicht antworten, Sonnenflecken, Gremlins, was auch immer.

In Java wird diese Art seltsamer Ereignisse, die Fehler in einem Programm auslösen können, als Ausnahmen (engl. Exceptions) bezeichnet. Java bietet in der Sprache einige Features, die sich mit den Ausnahmen beschäftigen. Darunter sind die folgenden:

Neben den Ausnahmen lernen Sie auch das System kennen, das mit Java 1.2 eingeführt wurde und es Applets ermöglicht, Dinge zu tun, die normalerweise Sicherheitsausnahmen erzeugen.

Programmieren im großen

Mit zunehmender Erfahrung im Java-Programmieren werden Sie feststellen, daß Sie nach dem Design der Klassen und Schnittstellen sowie der Methodendefinitionen immer noch keine Eigenschaften für Ihre Objekte definiert haben. Schließlich beschreibt eine Schnittstelle die übliche Verwendungsweise eines Objekts, beinhaltet aber keine seltsamen Ausnahmefälle. Bei vielen Systemen wird dieses Problem in der Dokumentation gelöst, z.B. durch Auflisten von Rückgabewerten, wie im vorherigen Beispiel. Da dem System darüber nichts bekannt ist, kann es keine Konsistenzprüfung durchführen. Der Compiler kann bei solchen Ausnahmebedingungen in keiner Weise eingreifen, im Gegensatz zu den hilfreichen Warnungen und Fehlermeldungen, die er produziert, wenn eine Methode fehlerhaft ist.

Dieser Aspekt wird im Programmdesign nicht berücksichtigt. Vielmehr sind Sie gezwungen, das irgendwie in der Dokumentation zu beschreiben, in der Hoffnung, daß keiner später bei der Implementierung einen Fehler macht. Das wird dadurch noch erschwert, daß jeder die gleiche Sache anders beschreibt. Sie benötigen also eine einheitliche Form der Deklaration der Absichten von Klassen und Methoden in bezug auf diese Ausnahmebedingungen. Java bietet eine solche Möglichkeit:

public class MyFirstExceptionalClass {
public void anExceptionalMethod() throws MyFirstException {
...
}
}

Hier wird der Leser (und der Compiler) darauf aufmerksam gemacht, daß der Code ... eine Ausnahme namens MyFirstException auswerfen kann.

Sie können sich die Beschreibung einer Methode als Vertrag zwischen dem Designer und der Methode (oder Klasse) und sich selbst als Aufrufer der Methode vorstellen. Normalerweise teilt diese Beschreibung die Typen der Argumente einer Methode, was sie ausgibt, und die allgemeine Semantik mit. Ihnen wird ebenfalls mitgeteilt, welche abnormen Dinge sie ausführen kann. Das ist ein Versprechen, genauso wie die Methode verspricht, einen Wert eines bestimmten Typs auszugeben, und Sie sich darauf verlassen können, wenn Sie den Code schreiben. Diese neuen Versprechen helfen, alle Stellen, an denen Ausnahmebedingungen in Ihrem Programm gehandhabt werden sollen, explizit zu bezeichnen.

Da Ausnahmen Instanzen von Klassen sind, können sie in eine Hierarchie gestellt werden, die auf natürliche Weise die Beziehungen zwischen den verschiedenen Ausnahmearten beschreibt. Wenn Sie sich die Klassenhierarchiediagramme von java.lang -Fehlern und java.lang-Ausnahmen ansehen, werden Sie feststellen, daß unter der Klasse Throwable zwei große Klassenhierarchien stehen. Die Wurzeln dieser zwei Hierarchien sind Subklassen von Throwable namens Exception und Error. Diese Hierarchien verkörpern die reichhaltigen Beziehungen, die zwischen Ausnahmen und Fehlern in der Java-Laufzeitumgebung bestehen.

Wenn Sie wissen, daß eine bestimmte Fehler- oder Ausnahmenart in Ihrer Methode auftreten kann, müssen Sie diese entweder selbst handhaben oder die potentiellen Aufrufer explizit mit der throws-Klausel darauf aufmerksam machen. Sie müssen nicht alle Fehler und Ausnahmen auflisten. Instanzen der Klasse Error oder RuntimeException (eine ihrer Subklassen) müssen in der throws-Klausel nicht aufgeführt werden. Sie werden besonders behandelt, weil sie irgendwo in einem Java-Programm auftreten können und normalerweise durch Bedingungen verursacht werden, die nicht auf den Programmierer zurückzuführen sind. Ein gutes Beispiel dafür ist OutOfMemoryError , ein Fehler, der jederzeit aus vielen Gründen auftreten kann.

Sie können diese Fehler und Laufzeitausnahmen auf Wunsch selbstverständlich auflisten, dann sind die Aufrufer Ihrer Methoden gezwungen, sie zu behandeln. Wenn das Wort »Exception« irgendwo allein steht, bedeutet es fast immer »Ausnahme oder Fehler« (d.h. eine Throwable-Instanz). In der obigen Diskussion wurde erklärt, daß Ausnahmen und Fehler im Grunde zwei getrennte Hierarchien bilden, sich abgesehen von der throws-Regel aber gleich verhalten.

Wenn Sie sich die Diagramme in Anhang B genauer ansehen, werden Sie feststellen, daß es nur fünf Ausnahmearten (in java.lang) gibt, die in einer throws-Klausel aufgelistet werden müssen (bedenken Sie, daß alle Errors und RuntimeExceptions Ausnahmen sind):

Jeder dieser Namen deutet etwas an, das explizit vom Programmierer veranlaßt wird, nicht etwas, das hinter den Kulissen abläuft, wie etwa OutOfMemoryError.

Unterhalb von java.util und java.io in der Klassenhierarchie sehen Sie, daß jedes Paket neue Ausnahmen hinzufügt. java.util fügt die zwei Ausnahmen ArrayStoreException und IndexOutOfBoundsException hinzu und stellt sie unter RuntimeException . java.io fügt einen ganzen Baum von IOExceptions hinzu, die eher vom Programmierer verursacht werden und deshalb unter der Wurzel Exception eingereiht werden. Somit muß IOExceptions in throws-Klauseln beschrieben werden. Schließlich definiert das Paket java.awt jeweils einen impliziten und einen expliziten Stil.

Die Java-Klassenbibliothek nutzt Ausnahmen überall sehr wirkungsvoll. Wenn Sie sich die ausführliche API-Dokumentation Ihres Java-Releases ansehen, sehen Sie, daß viele Methoden in der Bibliothek throws-Klauseln haben. Einige davon sind sogar dokumentiert (um sie dem Leser klarer darzustellen). Das ist lediglich auf die Nettigkeit des Verfassers zurückzuführen, denn von Ihnen wird nicht erwartet, in Ihren Programmen derartige Bedingungen zu berücksichtigen.

Programmieren im kleinen

Sie haben inzwischen schon ein gutes Gefühl bekommen, auf welche Weise Ausnahmen das Design eines Programms und einer Klassenbibliothek verbessern können. Wie aber werden Ausnahmen praktisch angewandt? Wir wollen das nun mit anExceptionalMethod() aus dem ersten Beispiel der heutigen Lektion versuchen:

public void anotherExceptionalMethod() throws MyFirstException {
MyFirstExceptionalClass aMFEC = new MyFirstExceptionalClass();
aMFEC.anExceptionalMethod();
}

Wir betrachten dieses Beispiel jetzt genauer. Da MyFirstException eine Subklasse von Exception ist, müssen Sie sie im Code von anotherExceptionalMethod() verarbeiten oder andernfalls die Aufrufer entsprechend warnen. Da Ihr Code anExceptionalMethod() lediglich aufruft, ohne die Tatsache zu berücksichtigen, daß MyFirstException ausgeworfen werden könnte, müssen Sie diese Ausnahme in Ihre throws- Klausel einfügen. Das ist absolut zulässig und verschont die Aufrufer vor etwas, für das Sie eigentlich zuständig sind (was selbstverständlich von den Umständen abhängt).

Nehmen wir an, Sie fühlen sich heute verantwortlich. Sie entschließen sich, die Ausnahme zu behandeln. Da Sie jetzt eine Methode ohne throws-Klausel deklarieren, müssen Sie die erwarteten Ausnahmen mit catch auffangen und etwas Nützliches damit anfangen:

public void responsibleMethod() {
MyFirstExceptionalClass aMFEC = new MyFirstExceptionalClass();
try {
aMFEC.anExceptionalMethod();
} catch (MyFirstException m) {
... // Tun Sie etwas schrecklich Wichtiges
}
}

Die try-Anweisung sagt praktisch alles: »Versuche, den Code innerhalb dieser Klammern auszuführen. Falls Ausnahmen ausgeworfen werden, sind entsprechende Handler dafür verfügbar.« Sie können am Ende von try beliebig viele catch-Klauseln einfügen. Mit jeder einzelnen können Sie die Ausnahmen, die in Instanzen vorkommen, handhaben. Mit catch in diesem Beispiel werden Ausnahmen der Klasse MyFirstException (oder einer ihrer Subklassen) gehandhabt.

Wenn Sie beide aufgezeigten Ansätze kombinieren, d.h. die Ausnahme selbst behandeln, sie aber auch den Aufrufern zur Kenntnis bringen wollen, können Sie das durch explizites Auswerfen der Ausnahme:

public void responsibleExceptionalMethod() throws MyFirstException {
MyFirstExceptionalClass aMFEC = new MyFirstExceptionalClass();
try {
aMFEC.anExceptionalMethod();
} catch (MyFirstException m) {
... // Etwas Verantwortungsvolles tun
throw m; // Erneutes Auswerfen der Ausnahme
}
}

Das funktioniert, weil Ausnahmen-Handler darin verschachtelt sind. Sie können die Ausnahme behandeln, indem Sie etwas Verantwortungsvolles mit ihr anfangen, entschließen sich aber, keinen Ausnahmen-Handler bereitzustellen, wenn die Aufrufer selbst eine Gelegenheit haben, sie zu behandeln. Ausnahmen fließen über die gesamte Kette der Methodenaufrufe (von denen die meisten normalerweise die Ausnahmen nicht behandeln werden), bis das System zuletzt eventuell nicht aufgefangene Ausnahmen selbst behandelt, indem es das Programm beendet und eine Fehlermeldung ausgibt. In einem Einzelprogramm ist das nicht einmal so schlecht. In einem Applet aber kann das den Browser zum Absturz bringen. Die meisten Browser schützen sich vor dieser Katastrophe, indem sie alle Ausnahmen beim Ausführen eines Applets selbst auffangen, sicher ist das aber nicht. Die Faustregel lautet deshalb: Falls es Ihnen möglich ist, eine Ausnahme aufzufangen, sollten Sie das auch tun.

Wir betrachten nun, wie das Auswerfen einer neuen Ausnahme aussieht. Wir schlachten das Beispiel der ersten Lektion weiter aus:

public class MyFirstExceptionalClass {
public void anExceptionalMethod() throws MyFirstException {
...
if (someThingUnusualHasHappened()) {
throw new MyFirstException();
// Die Ausführung kommt nicht bis hierher
}
}
}

throw ist mit der break-Anweisung vergleichbar - was danach folgt, wird nicht mehr ausgeführt.

Das ist eine grundlegende Möglichkeit, alle Ausnahmen zu erzeugen. Die ganze Hierarchie unter der Klasse Throwable wäre nichts wert, wenn man die throw-Anweisung nicht überall im Code verwenden könnte. Da sich Ausnahmen über jede Tiefe bis in die Methoden hinein ausbreiten können, kann jeder Methodenaufruf theoretisch eine Fülle möglicher Fehler und Ausnahmen erzeugen. Zum Glück muß man nur die in der throws-Klausel der jeweiligen Methode auflisten. Der Rest wandert stillschweigend zur Ausgabe einer Fehlermeldung (oder bis er vom System aufgefangen wird).

Im folgenden ungewöhnlichen Beispiel wird das aufgezeigt, wobei throw und der auffangende Handler sehr eng zusammenliegen:

System.out.print("Now ");
try {
System.out.print("is ");
throw new MyFirstException();
System.out.print("a ");
} catch (MyFirstException m) {
System.out.print("the ");
}
System.out.print("time.");

Dieser Code gibt die Zeichenkette Now is the time. aus.

Ausnahmen sind eine starke Technik, um den Bereich aller möglichen Fehlerbedingungen in handhabbare Stücke aufzuteilen. Da die erste passende catch-Klausel ausgeführt wird, können Sie Ketten wie beispielsweise folgende erstellen:

try {
someReallyExceptionalMethod();
} catch (NullPointerException n) {// Eine Subklasse von RuntimeException
...
} catch (RuntimeException r) { // Eine Subklasse von Exception
...
} catch (IOException i) { // Eine Subklasse von Exception
...
} catch (MyFirstException m) { // Unsere Subklasse von Exception
...
} catch (Exception e) { // Eine Subklasse von Throwable
...
} catch (Throwable t) {
... // Fehler, plus alles, was oben nicht 
// aufgefangen wurde
}

Indem Subklassen vor ihren Elternklassen aufgelistet werden, fangen die Eltern alles auf, was nicht von einer der darüberstehenden Subklassen aufgefangen wurde. Mit Ketten dieser Art können Sie fast alle Testkombinationen ausdrücken. Findet etwas wirklich Seltsames statt, das Sie nicht behandeln können, kann dies eventuell durch Verwendung einer Schnittstelle aufgefangen werden. Dadurch können Sie Ihre Ausnahmenhierarchie unter Verwendung der Mehrfachvererbung auslegen. Das Auffangen einer Schnittstelle anstatt einer Klasse eignet sich auch zum Testen einer Eigenschaft, die in vielen Ausnahmen vorkommt, in einem Einfachvererbungsbaum aber nicht ausgedrückt werden kann.

Nehmen wir beispielsweise an, daß mehrere in Ihrem Code verteilte Ausnahmenklassen nach dem Auswerfen einen Neustart voraussetzen. Sie können eine Schnittstelle namens NeedsReboot erstellen, so daß alle Ausnahmenklassen die Schnittstelle implementieren. (Keine davon braucht eine Elternklasse.) Dann fängt der Ausnahmen- Handler auf der höchsten Ebene Klassen auf, die NeedsReboot implementieren, und führt einen Neustart aus:

public interface NeedsReboot { }// Braucht überhaupt keinen Inhalt
try {
someMethodThatGeneratesExceptionsThatImplementNeedsReboot();
} catch (NeedsReboot n) { // Schnittstelle auffangen
... // Aufräumen
SystemClass.reboot(); // Neustart anhand einer erfundenen
   Systemklasse
}

Übrigens, wenn Sie wirklich ungewöhnliche Verhaltensweisen während einer Ausnahme brauchen, können Sie diese in die Ausnahmenklasse einfügen! Denken Sie daran, daß eine Ausnahme eine normale Klasse ist. Deshalb kann sie Instanzvariablen und Methoden enthalten. Deren Verwendung ist zwar ein bißchen seltsam, kann sich bei absonderlichen Situationen aber als nützlich erweisen. Das könnte etwa so aussehen:

try {
someExceptionallyStrangeMethod();
} catch (ComplexException e) {
switch (e.internalState()) {// Wahrscheinlich der Wert einer 
// Instanzvariablen
case e.COMPLEX_CASE: // Eine Klassenvariable der Ausnahme
e.performComplexBehavior(myState, theContext, etc);
break;
...
}
}

Einschränkungen beim Programmieren

So leistungsstark sich das alles anhört, kann man sich des Eindrucks nicht erwehren, daß es gewisse Einschränkungen auferlegt, stimmt`s? Nehmen wir beispielsweise an, Sie wollen die Standardmethode toString() der Object-Klasse überschreiben, um sich einen Einblick zu verschaffen, was eigentlich alles ausgegeben wird:

public class MyIllegalClass {
public String toString() {
someReallyExceptionalMethod();
... // Gibt eine Zeichenkette aus
}
}

Da die Superklasse Object die Methodendeklaration für toString() ohne throws- Klausel definiert, muß eine Implementierung davon in einer Subklasse dieser Einschränkung gehorchen. Insbesondere können Sie nicht einfach someReallyExceptionalMethod() wie zuvor aufrufen, weil sie eine Fülle von Fehlern und Ausnahmen erzeugt, von denen einige in der throws-Klausel aufgelistet werden (z.B. IOException und MyFirstException). Würden keine ausgeworfenen Ausnahmen in der Liste stehen, hätten Sie kein Problem. Da einige aber darin aufgeführt werden, müssen Sie mindestens diese mit catch auffangen:

public class MyLegalClass {
public String toString() {
try {
someReallyExceptionalMethod();
} catch (IOException e) {
} catch (MyFirstException m) {
}
... // Gibt eine Zeichenkette aus
}
}

In beiden Fällen werden die Ausnahmen zwar aufgefangen, jedoch wird absolut nichts unternommen. Das ist zulässig, aber nicht immer die richtige Entscheidung. Man sollte sich einige Gedanken machen, um das beste nichttriviale Verhalten einer bestimmten catch-Klausel festzulegen. Diese zusätzliche Denkarbeit macht Ihr Programm robuster, kann ungewöhnliche Eingaben leichter handhaben und funktioniert im Zusammenhang mit Multithreading besser.

Die toString()-Methode von MyIllegalClass produziert einen Kompilierfehler. Wenn Sie sich darüber einige Gedanken machen, um die Situation optimal zu lösen, werden Sie reichlich dafür belohnt, denn Ihre Klassen können dann in späteren Projekten bzw. größeren Programmen wiederverwendet werden. Selbstverständlich wurde die Java-Bibliothek mit genau dieser Sorgfalt entwickelt. Unter anderem ist sie auch deshalb robust, so daß vielseitige Java-Projekte entwickelt werden können.

Die finally-Klausel

Nehmen wir an, es gibt eine Aktion, die Sie unbedingt ausführen müssen, egal was passiert. Normalerweise ist das das Freigeben von externen Ressourcen, die beansprucht wurden, oder eine Datei zu schließen usw. Um sicherzugehen, daß »egal was passiert« auch Ausnahmen beinhaltet, benutzen Sie eine Klausel der try-Anweisung, die genau für diese Zwecke entwickelt wurde - finally:

SomeFileClass f = new SomeFileClass();
if (f.open("/a/file/name/path")) {
try {
someReallyExceptionalMethod();
} finally {
f.close();
}
}

Diese Anwendung von finally verhält sich ungefähr so:

SomeFileClass f = new SomeFileClass();
if (f.open("/a/file/name/path")) {
try {
someReallyExceptionalMethod();
} catch (Throwable t) {
f.close();
throw t;
}
}

ausgenommen, daß finally hier auch zum Aufräumen nach Ausnahmen und nach return-, break- und continue-Anweisungen benutzt werden kann. Nachfolgend eine komplexe Demonstration:

public class MyFinalExceptionalClass extends ContextClass {
public static void main(String argv[]) {
int mysteriousState = getContext();
while (true) {
System.out.print("Who ");
try {
System.out.print("is ");
if (mysteriousState == 1)
return;
System.out.print("that ");
if (mysteriousState == 2)
break;
System.out.print("strange ");
if (mysteriousState == 3)
continue;
System.out.print("but kindly ");
if (mysteriousState == 4)
throw new UncaughtException();
System.out.print("not at all ");
} finally {
System.out.print("amusing ");
}
System.out.print("yet compelling ");
}
System.out.print("man?");
}
}

Dieser Code erzeugt je nach dem Wert von mysteriousState eine der folgenden Ausgaben:

1 Who is amusing
2 Who is that amusing man?
3 Who is that strange amusing Who is that strange amusing ...
4 Who is that strange but kindly amusing
5 Who is that strange but kindly not at all amusing yet compelling man?

In Fall 3 endet die Ausgabe erst, wenn (Strg)+(C) gedrückt werden. In Fall 4 wird auch eine von UncaughtException erzeugte Fehlermeldung ausgegeben.

Digitale Signaturen zur Identifikation von Applets

Die Strategie für Sicherheit von Java-Applets geht davon aus, daß Sie niemandem im World Wide Web trauen können. Diese Denkweise mag zynisch klingen, in der Praxis bedeutet sie folgendes: Potentiell kann jeder ein Java-Applet erstellen, das Schaden anrichtet, der Benutzer hat keine Chance, sich dagegen zu wehren. Deshalb wurden von Anfang an alle Funktionen der Sprache, die einen Mißbrauch ermöglichen, zur Verwendung bei Applets ausgeschlossen. Darunter fallen:

Java 1.2 bietet eine Möglichkeit an, wie sich alle Funktionen, die für Java-Anwendungen zur Verfügung stehen, auch für Applets einsetzen lassen, sofern sie von einem vertrauenswürdigen Applet-Hersteller stammen und eine digitale Signatur enthalten, welche die Authentizität bestätigen. Eine digitale Signatur ist eine verschlüsselte Datei, die ein Programm begleitet und genau angibt, woher das Programm stammt. Benutzer, die wissen, wer ein Programm hergestellt hat, können dann selbst darüber entscheiden, ob sie dieser Firma oder dem einzelnen Hersteller vertrauen. Wer bereits mit ActiveX-Steuerelementen vertraut ist, kennt dieses System, mit dem auch ActiveX-Steuerelemente auf Seiten im World Wide Web zur Verfügung gestellt werden.

Ein Dokument, das eine digitale Signatur repräsentiert, wird Zertifikat genannt (engl. certificate).

Damit ein Applet-Hersteller vertrauenswürdig wird, muß er seine Identität gegenüber einer Gruppe, die als Zertifizierungsautorität (engl. certificate authority) bezeichnet wird, verifizieren. Idealerweise sind diese Gruppen unabhängig von den Applet-Herstellern und sollten in dem Ruf einer verläßlichen Firma stehen. Momentan bieten die folgenden Firmen Authentifizierungsdienste in irgendeiner Form an:

Andere Firmen bieten Zertifizierungsdienste für Kunden in bestimmten geografischen Gebieten. Netscape listet die Zertifizierungsautoritäten, mit denen sie zusammenarbeiten, unter der folgenden Web-Adresse auf:

https://certs.netscape.com/client.html

Das allgemeine Sicherheitsmodell, das hier beschrieben wird, ist das offizielle, das von JavaSoft erstellt wurde. JavaSoft nutzt dieses im eigenen Browser und alle Browser, die voll kompatibel zu Java 1.2 sind, nutzen es ebenfalls. Netscape und Microsoft haben ihre eigenen Sicherheitsmodelle für die eigenen Browser vorgestellt. Das heißt, ein Applet muß für jeden Browser, in dem es laufen soll, das entsprechende System implementieren. Glücklicherweise sind sich die Systeme ähnlich, so daß es einfacher wird, die anderen zu lernen, sobald man eines gemeistert hat.

Es ist ebenfalls möglich, Sicherheitsstufen zwischen absolut vertrauenswürdig (das Applet darf alles machen) und kein Vertrauen (das Applet darf nichts tun, was eventuell Schaden anrichten könnte) festzulegen. Java 1.2 ermöglicht dies über eine Reihe von Klassen, die als Permissions bezeichnet werden.

Im Moment unterliegen Applets allen Einschränkungen, bis der Entwickler das Applet digital signiert und der Benutzer diesen Entwickler als vertrauenswürdig einstuft.

Ein Beispiel mit einer digitalen Signatur

Um das Prozedere für eine sichere Applet-Übertragung besser zu verstehen, soll für dieses Beispiel folgendes Szenario gelten: Der Entwickler des Applets heißt Fishhead Software, es gibt eine unabhängige Java-Gruppe namens Signatures R` US und einen Web-Benutzer namens Gilbert.

Fishhead Software bietet auf seiner Website ein Applet-Spiel an, das High Scores und andere Informationen auf die Festplatte des Benutzers schreibt. Dies ist normalerweise nicht möglich, weil die Applet-Beschränkungen einen Zugriff auf die Festplatte des Benutzers ausschließen. Damit das Spiel dennoch gespielt werden kann, muß Fishhead Software das Applet mit einer digitalen Signatur versehen. Auf diese Weise können die Benutzer Fishhead Software als vertrauenswürdigen Software-Hersteller identifizieren.

Dieser Vorgang wird in fünf Schritten ausgeführt:

1. Fishhead Software verwendet keytool, ein Tool aus dem JDK, welches zwei verschlüsselte Dateien erstellt, die öffentlicher Schlüssel und privater Schlüssel heißen. Zusammen bilden beide Schlüssel eine elektronische »ID-Karte«, mit deren Hilfe sich der Hersteller komplett identifizieren läßt. Fishhead Software stellt sicher, daß niemand anders auf den privaten Schlüssel Zugriff erhält. Der öffentliche Schlüssel kann und sollte jedem als Teil der ID zugänglich gemacht werden.

2. Fishhead Software benötigt eine Instanz, die beglaubigen kann, wer die Firma ist. Es schickt seinen öffentlichen Schlüssel und eine beschreibende Datei zu Fishhead Software an eine unabhängige Gruppe, der die Java-Benutzer trauen. Diese Gruppe ist Signatures R` US.

3. Signatures ,R` US überprüft Fishhead Software mit dem öffentlichen Schlüssel, um sicherzustellen, daß es sich um eine legitime Software-Firma handelt. Wenn Fishhead Software Muster verschickt, erstellt Signatures R` US eine neue verschlüsselte Datei, die Zertifikat genannt wird. Dieses Zertifikat wird an Fishhead Software zurückgeschickt.

4. Fishhead erstellt eine Java-Archivdatei für das Applet-Spiel und alle zugehörigen Dateien. Mit einem öffentlichen Schlüssel, einem privaten Schlüssel und dem Zertifikat kann Fishhead Software nun das Tool jar dazu verwenden, die Archivdatei mit einer digitalen Signatur zu versehen.

5. Fishhead Software plaziert das gekennzeichnete Archiv so auf der Website, daß es sich zusammen mit dem öffentlichen Schlüssel herunterladen läßt.

Damit hat Fishhead Software das Spiel in einer Weise zur Verfügung gestellt, die es den Benutzern ermöglicht, seine Sicherheitsbedenken auszuräumen. Eine dieser Personen, die sich dazu entschlossen haben, das Applet-Spiel von Fishhead zu laden, ist der Web-Benutzer Gilbert. Er arbeitet mit einem Browser, der Java 1.2 unterstützt.

Seine Aktionen sind einfacher:

1. Gilbert erkennt, daß das neue Applet-Spiel von Fishhead Risiken birgt und er möchte zunächst wissen, ob es sich bei dem Hersteller um eine vertrauenswürdige Firma handelt. Er lädt den öffentlichen Schlüssel von Fishhead herunter.

2. Er beschließt, daß es sich bei Fishhead Software um eine vertrauenswürdige Firma handelt und verwendet jarsigner zusammen mit dem öffentlichen Schlüssel dazu, die Firma in seine Systemliste für sichere Hersteller aufzunehmen.

Gilbert kann das Applet-Spiel nach Herzenslust spielen. Dieses Spiel kann alle Funktionen ausführen, die andernfalls nur Java-Anwendungen vorbehalten sind. Das heißt: Selbstverständlich kann dieses Spiel immer noch alle nur denkbaren Schäden auf seinem System anrichten, ebenso wie jede andere Software auch, die auf einem Computer installiert wird. Der Vorteil der digitalen Signatur besteht aber darin, daß der Benutzer weiß, welcher Programmierer das Spiel erstellt hat. Programmierer, die Viren verteilen möchten, werden wohl kaum eine Spur hinterlassen, die direkt zu ihrem Haus weist.

Vielleicht fragen Sie sich, warum es einen öffentlichen und einen privaten Schlüssel gibt? Wenn nur beide Schlüssel zusammen eine Firma identifizieren können, wie ist es dann möglich, daß der öffentliche Schlüssel allein zur Identifikation von Fishhead dient?

Ein öffentlicher und ein privater Schlüssel ergeben zusammen ein Sicherheitssystem. Da nur beide zusammen Fishhead Software ganz identifizieren, sind beide Schlüssel nur der Firma selbst zugänglich, andernfalls könnte sich jemand anders als Fishhead ausgeben und niemand könnte dies enthüllen. Indem Fishhead seinen privaten Schlüssel schützt, schützt es seine Identität und seine Reputation.

Die Prüfung, die Signatures ,R` US unter Verwendung des öffentlichen Schlüssels durchführt, stellt vor allem sicher, daß der öffentliche Schlüssel wirklich zu dieser Firma gehört. Da der öffentliche Schlüssel an jeden weitergegeben werden kann, stellt Fishhead diesen auf der Website zur Verfügung. Als Teil des Zertifizierungsvorgangs kann Signatures ,R` US diesen herunterladen und mit jenem vergleichen, den es erhalten hat. Die Zertifizierungsgruppe agiert als eine Art Ersatz für den privaten Schlüssel und stellt sicher, daß der öffentliche Schlüssel legitim ist. Das herausgegebene Zertifikat ist mit dem öffentlichen Schlüssel verbunden, der nur zusammen mit dem privaten Schlüssel von Fishhead verwendet werden kann.

Jeder, der mit dem Tool keytool arbeitet, kann ein Zertifikat für einen öffentlichen Schlüssel herausgeben. Fishhead könnte sich das Zertifikat auch selbst ausstellen. Doch mit einem solchen Schritt würde die Glaubwürdigkeit deutlich herabgesetzt. Eine bekannte, unabhängige Zertifizierungsgruppe sorgt für Vertrauenswürdigkeit.

Alle Elemente zusammen bilden eine verläßliche digitale Signatur für ein Java-Archiv. Die JavaSoft-Dokumentation zu keytool, der Applet-Kennzeichnung jar und andere neue Sicherheitsfunktionen stehen unter folgender Web-Adresse zur Verfügung:

http://www.javasoft.com/products/JDK/1.2/docs/guide/security

Browserspezifische Signaturen

Zu dem Zeitpunkt, als dieses Buch entstand, war die einzige Möglichkeit, ein Applet zu signieren, die Methoden anzuwenden, die die Entwickler bei Netscape und Microsoft für deren eigene Browser entwickelt haben. Sie müssen deren Tools verwenden und ein Applet mit beiden Methoden signieren, wenn Sie die Benutzer beider Browser erreichen wollen.

Um ein Applet für den Microsoft Internet Explorer zu signieren, ist folgendes nötig:

Um ein Applet für den Netscape Navigator zu signieren, ist folgendes nötig:

Dokumentationsmaterial zur Anwendung dieser Tools finden Sie auf den Websites, von denen Sie diese Tools herunterladen. Zusätzlich hat Daniel Griscom von Suitable Systems eine exzellente Informationsquelle für das Signieren von Java-Code unter der folgenden Web-Adresse zusammengestellt: http://www.suitable.com/Doc_CodeSigning.shtml

Sicherheitsrichtlinien

Vor Java 1.2 ging man davon aus, daß alle Applikationen als gänzlich vertrauenswürdig eingestuft werden und in der Lage sein sollten, alle Features der Sprache zu verwenden - dies war auch entsprechend in die Sprache integriert.

Um es einfacher zu gestalten, Applikationen zu erstellen, die in ihren Möglichkeiten eingeschränkter sind, werden Applikationen inzwischen derselben Sicherheitsüberprüfung wie Applets unterzogen.

In der allgemeinen Praxis ändert dies nichts daran, wie Applikationen geschrieben und ausgeführt werden - die Applikationen, die Sie im Laufe des Buches erstellt haben, sollten keine sicherheitsbezogenen Ausnahmen verursacht haben, als Sie diese auf Ihrem System ausgeführt haben. Der Grund dafür ist, daß die Sicherheitsrichtlinien bei der Installation des JDK so liberal wie möglich angelegt wurden, so daß es der Applikation möglich ist, alle Features zu nutzen.

Die Sicherheitsrichtlinien werden in der Datei java.policy gespeichert. Diese Datei finden Sie in dem Unterordner lib\security\ des Hauptordners der JDK-Installation. Diese Datei kann mit jedem Texteditor bearbeitet werden, obwohl Sie diese nicht verändern sollten, solange Sie nicht über die Funktionsweise gut Bescheid wissen. Sie können auch ein Tool mit grafischer Oberfläche zur Bearbeitung der Richtlinien verwenden. Dieses trägt den Namen policytool.

Einen Überblick über die Sicherheitsfeatures, die in Java 1.2 implementiert wurden, steht bei JavaSoft auf der folgenden Webseite zur Verfügung:

http://java.sun.com/products/jdk/1.2/docs/guide/security/spec/security-spec.doc.html

Zusammenfassung

Heute haben Sie das Arbeiten mit Ausnahmen gelernt und erfahren, daß Sie damit Robustheit in Ihrem Programmdesign erreichen können.

Sie haben eine Fülle von Ausnahmen kennengelernt, die in der Java-Klassenbibliothek definiert sind. Sie haben die Verwendung von throw, try und catch gelernt, um mögliche Fehler und Ausnahmen zu handhaben. Ferner haben Sie gesehen, daß die ausschließliche Arbeit mit der Java-Ausnahmenhandhabung dem Programmierer einige Einschränkungen auferlegt. Sie haben aber auch erfahren, daß diese Einschränkungen in anderer Hinsicht reich belohnt werden.

Schließlich haben Sie die finally-Klausel gelernt, mit der Sie sicher sein können, etwas zu erreichen, egal was.

Außerdem haben Sie heute die Grundlagen gelernt, wie das Sicherheitsmodell von Java 1.2 implementiert ist, und welche Möglichkeiten die verschiedenen Browserhersteller anbieten, um die normalen Sicherheitseinschränkungen von Applets mit digitalen Signaturen zu umgehen.

Fragen und Antworten

Frage:
Ich habe den Unterschied zwischen Exceptions, Errors und RuntimeExceptions noch nicht verstanden. Könnten Sie das nochmal auf andere Weise erklären?

Antwort:
Errors werden durch Probleme beim dynamischen Linken oder der virtuellen Maschine verursacht und sind deshalb für die meisten Programme auf einer zu niedrigen Ebene, um sie behandeln zu können (obwohl ausgefeilte Entwicklungsbibliotheken und Umgebungen wahrscheinlich dazu in der Lage sind). RuntimeExceptions werden durch die normale Ausführung des Java-Codes erzeugt. Sie spiegeln zwar gelegentlich eine Bedingung wider, die explizit gehandhabt werden soll oder kann, sind aber meist auf einen Programmierfehler zurückzuführen und sollen lediglich eine Fehlermeldung ausgeben, damit der Fehler abgegrenzt werden kann. Exceptions, die nicht zu RuntimeExceptions gehören (z.B. IOExceptions), sind Bedingungen, die aufgrund ihrer Natur explizit durch einen robusten und gut durchdachten Code gehandhabt werden sollten. Die Java-Klassenbibliothek enthält einige davon, die extrem wichtig sind, um das System sicher und korrekt benutzen zu können. Der Compiler unterstützt Sie bei der Handhabung dieser Ausnahmen durch Kontrollen und Einschränkungen über die throws-Klausel.

Frage:
Gibt es eine Möglichkeit, die straffen Einschränkungen, denen Methoden durch die throws-Klausel unterliegen, irgendwie zu umgehen?

Antwort:
Ja, die gibt es. Nehmen wir an, Sie haben lange genug nachgedacht und sind fest entschlossen, diese Einschränkung zu umgehen. Das ist fast nie der Fall, weil die richtige Lösung die ist, Ihre Methoden neu auszulegen, um die auszuwerfenden Ausnahmen entsprechend zu berücksichtigen. Wir stellen uns aber vor, daß Sie durch eine Systemklasse aus irgendeinem Grund in einer Zwangsjacke stecken. Ihre erste Lösung ist, von RuntimeException eine Subklasse für Ihre spezielle Ausnahme zu erstellen. Sie können damit nach Herzenslust Ausnahmen auswerfen, denn die throws-Klausel, die Sie belästigt hat, muß diese neuen Ausnahmen nicht enthalten. Müssen Sie viele solcher Ausnahmen unterbringen, besteht ein eleganter Ansatz darin, einige neue Ausnahmenschnittstellen in Ihre neuen Runtime-Klassen beizumischen. Welche dieser neuen Schnittstellen Sie mit catch auffangen wollen, steht Ihnen frei (keine der normalen Runtime-Ausnahmen muß aufgefangen werden), während eventuelle Überbleibsel der (neuen) Runtime-Ausnahmen (absolut zulässig) diese andernfalls lästige Standardmethode der Bibliothek durchlaufen können.

Frage:
Ich finde es zuweilen ganz schön lästig, Ausnahmebedingungen zu handhaben. Was kann mich eigentlich davon abhalten, mich durch eine Methode mit einer throws-Klausel abzuschotten? Beispielsweise so:

try { that AnnoyingMethod(); } catch (Throwable t) { }

Damit könnte ich doch alle Ausnahmen einfach ignorieren, oder?

Antwort:
Sie würden damit nichts außer Ihrem Gewissen abschotten. In manchen Fällen sollen Sie nichts unternehmen, weil das einfach für die Implementierung der betreffenden Methode das Richtige ist. In anderen Fällen müssen Sie sich aber wohl oder übel durch diese lästigen Prozeduren durchkämpfen. Sie gewinnen dadurch ja auch mehr Erfahrung. Auch der beste Programmierer sollte sich vor solchen öden Dingen nicht scheuen, zumal er reichlich dafür belohnt wird.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Java 2 in 21 Tagen, ISBN: 3-8272-5578-3