Weitere aktuelle Java-Titel finden Sie bei dpunkt.
 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index


15.2.4

Der Java Authentication and Authorization Service (JAAS)



Mit dem [1.4]Java Authentication and Authorization Service (JAAS) steht in Java ein API zur Anmeldung von Benutzern an ein Java-Programm zur Verfügung. Die Architektur von JAAS wurde wesentlich von den bei Unix bekannten Pluggable Authentication Modules (PAM) geprägt. Die Idee von PAM ist, die Authentifzierung nicht »hart« in der Anwendung zu kodieren, sondern in externe Module mit einheitlicher Schnittstelle auszulagern. Welches Modul zum Einsatz kommt, wird extern konfiguriert. Auf diese Weise kann das Authentifizierungsverfahren sehr einfach durch einen Austausch des PAM-Moduls gewechselt werden, ohne Änderungen an der Anwendung vornehmen zu müssen. Das gleiche gilt auch für die Authentifizierungsmodule von JAAS. Im Wesentlichen bietet JAAS die folgenden Vorteile: Der nächste Abschnitt behandelt die Benutzung der Standard-Module von JAAS sowie ein Beispiel für ein anwenderdefiniertes Modul. Am Schluss wird gezeigt, wie man eine authentifizierte Benutzerkennung verwenden kann, um erweiterte Privilegien zu erhalten.

Authentifizierung

Das Prinzip von JAAS ist, dass dem Benutzer, der die Anwendung ausführt (in der JAAS-Terminologie »Subject« genannt) eine oder mehrere authentifizierte Benutzerkennungen (als »Principals« bezeichnet) zugeordnet werden. Ein Principal kann z. B. eine erfolgreiche Anmeldung an Windows sein, die der Benutzer vor dem Starten der Anwendung durchgeführt hat. In diesem Fall verknüpft das entsprechende JAAS-Login-Modul das Subject unter anderem mit einem Exemplar der Klasse NTUserPrincipal, das die Windows-Benutzer-ID darstellt. Daneben stellt dieses Modul noch weitere Principals aus (z. B. für die Domäne und die Gruppen, denen der Benutzer angehört).

Die Schnittstelle zwischen JAAS und einem Programm, das JAAS benutzt, ist die Klasse LoginContext aus dem Paket javax.security.auth.login. Ein LoginContext benötigt zwei Informationen: Für die Verwendung von JAAS sind daher drei Schritte durchzuführen: Ein einfaches Beispiel für die Modulkonfiguration könnte so aussehen:
Demo {
  // Für Windows:
  com.sun.security.auth.module.NTLoginModule required debug=false;
  // Für Unix:
  // com.sun.security.auth.module.UnixLoginModule required debug=false;
};
Ein Eintrag beginnt mit den Namen der Konfiguration, dem die gewünschten Module in geschweiften Klammern eingefasst folgen. Wichtig ist, dass jeder Eintrag und auch das Klammerpaar mit einem Semikolon abgeschlossen werden. Wie in dem Beispiel zu sehen ist, können den Modulen auch Parameter übergeben werden.

Das obige Beispiel bindet ein Login-Modul ein, das standardmäßig bei J2SE dabei ist, und Principals für die Windows- bzw. Unix-Benutzer-ID ausstellt, unter der das Programm ausgeführt wird. Diese beiden Module erfordern keine Benutzerinteraktion und somit auch keinen speziellen CallbackHandler, sondern erzeugen die Principals automatisch und reichen die Betriebssystem-Benutzerkennung an die Anwendung weiter. Auf diese Weise schaffen diese beiden Module auch eine Art Single-Sign-On mit dem Betriebssystem.

Damit eine Modul-Konfigurationsdatei aktiv wird, muss sie in der System-Property java.security.auth.login.config eingetragen werden. Hierzu kann der Pfad entweder als Parameter des Interpreters java mit der Option -D oder im Programm mit System.setProperty() gesetzt werden:
  System.setProperty("java.security.auth.login.config", "jaasdemo.conf");
Falls in der Konfiguration kein Eintrag mit dem gewünschten Namen vorhanden ist, wird automatisch versucht, einen Eintrag mit dem Namen other zu finden. Existiert dieser auch nicht, kommt es zu einer Exception.

Wie erwähnt, benötigt weder das NTLoginModule noch das UnixLoginModule einen CallbackHandler, da sie nicht interaktiv arbeiten. Daher kann die Angabe eines CallbackHandler bei der Erzeugung des LoginContext entfallen (wodurch ein Default-CallbackHandler aktiv wird, dessen Klassenname in der System-Property auth.login.defaultCallbackHandler eingestellt werden kann). Das folgende Minimalprogramm zur Durchführung eines Logins zeigt nach dem Login die vom Modul vergebenen Principals an:
  // Login-Kontext für die Konfiguration "Demo" erzeugen
  try {
    loginContext = new LoginContext("Demo");
  }
  catch (LoginException e) {
    System.err.println("login context creation failed: "+e.getMessage());
    System.exit(1);
  }
  // Durchführung des Logins
  try {
    loginContext.login();
  }
  catch (LoginException e) {
    System.out.println("authentication failed");
    System.exit(1);
  }
  System.out.println("authentication succeeded");

  // Die Principals ermitteln...
  Set principals = loginContext.getSubject().getPrincipals();
  // ...und in einer Iteration ausgeben
  Iterator it = principals.iterator();
  Principal p;
  while (it.hasNext()) {
    p = (Principal)it.next();
    System.out.println(p);
  }
Ein Beispiel für einen benutzerdefinierten CallbackHandler wird im nächsten Abschnitt gezeigt.

Neben den beiden genannten Modulen werden standardmäßig noch folgende Module mitgeliefert:

Material zum Beispiel

Kaskadierung von Login-Modulen

Bei JAAS können Login-Module auch kaskadiert, d. h. nacheinander ausgeführt werden. Das folgende Beispiel erweitert die vorhergehende Konfiguration um eine Anmeldung mit dem Modul MyLoginModule.
Demo {
  com.sun.security.auth.module.NTLoginModule required debug=false;
  demo.MyLoginModule required debug=false;
};
Damit wird zunächst eine Anmeldung mit dem NTLoginModule und danach mit MyLoginModule durchgeführt.

Mit der Verkettung von Login-Modulen kommt das zweite Grundmerkmal von JAAS zum Tragen, dem von Datenbank-Transaktionen her bekannten Zweiphasen-Prinzip. In der ersten Phase werden die Login-Informationen ermittelt. Dies kann im einfachsten Fall durch eine Abfrage von Benutzername und Passwort erfolgen. Erst wenn alle erforderlichen Module diese erste Phase erfolgreich durchlaufen haben, beginnt die zweite Phase, in der dem Subject die Principals hinzugefügt werden. Daher wird bei JAAS zwischen dem Ergebnis eines einzelnen Moduls und dem Ergebnis der gesamten Kette unterschieden. Bei JAAS kann für jedes einzelne Login-Modul konfiguriert werden, ob die Principals nur dann gewährt werden, wenn die gesamte Kette erfolgreich war, oder ob es bereits genügt, wenn das Modul selbst abgeschlossen wurde.

Diese Einstellung erfolgt über Tags in den Einträgen der Modulkonfiguration. Die einzelnen Tags legen dabei fest, ob das betreffende Modul Erfolg haben muss, damit die Gesamtanmeldung noch als erfolgreicht gilt. Es kann auch definiert werden, ob nachfolgende Module überhaupt noch zur Ausführung kommen sollen. Die Tags sind im Einzelnen:
requiredDie Authentifizierung des Moduls muss erfolgreich sein, damit die Gesamtauthentifizierung erfolgreich ist. Nachgeordnete Login-Module werden in jedem Fall noch ausgeführt, unabhängig davon, ob die Anmeldung mit diesem Modul fehlschlägt.
requisiteDie Authentifizierung des Moduls muss erfolgreich sein, damit die Gesamtauthentifizierung erfolgreich ist. Falls die Anmeldung mit diesem Modul fehlschlägt, ist auch die Gesamtauthentifizierung fehlgeschlagen und login() kehrt sofort zurück. Ist die Anmeldung mit diesem Modul dagegen erfolgreich, werden nachgeordnete Login-Module auch noch ausgeführt.
sufficientDie Authentifizierung mit dem Modul braucht nicht zwingend erfolgreich sein, damit die Gesamtauthentifizierung erfolgreich ist. Ist sie erfolgreich, gilt die Gesamtauthentifizierung als erfolgreich und login() kehrt sofort zurück. Schlägt die Anmeldung mit diesem Modul fehl, werden nachgeordnete Login-Module auch noch ausgeführt.
optionalDie Authentifizierung mit dem Modul braucht nicht zwingend erfolgreich sein, damit die Gesamtauthentifizierung erfolgreich ist. Nachgeordnete Login-Module werden in jedem Fall noch ausgeführt, unabhängig davon, ob die Anmeldung mit diesem Modul fehlschlägt.
Tabelle 15.2 zeigt die Tags noch einmal im Überblick:

Tabelle 15.2: Tags für die Login-Module
TagErfolg zwingendAusführung folgender Module
requiredjaimmer
requisitejanur bei Erfolg
sufficientneinnur bei Mißerfolg
optionalneinimmer

Module, die als sufficient oder optional definiert sind, beeinflussen das Gesamtergebnis nur, wenn keine required- oder requisite-Module konfiguriert sind. In diesem Fall muss mindestens eine sufficient- oder optional-Modul erfolgreich sein, damit der gesamte Anmeldevorgang als erfolreich gilt.

Die folgende Konfiguration erfordert die erfolgreiche Ausführung des NTLoginModule. Nur wenn dieses Erfolg hat, kann nachgelagert eine optionale Anmeldung mit MyLoginModule erfolgen.
  Demo {
    com.sun.security.auth.module.NTLoginModule requisite debug=false;
    demo.MyLoginModule optional debug=false;
  };

Implementierung eigener Login-Module

Eigene Login-Module können durch Implementierung des Interface LoginModule aus dem Paket javax.security.auth.spi erstellt werden. In der Regel wird man auch eine entsprechende Unterklasse von Principal entwickeln, um die von dem Modul erzeugte Benutzerkennung darzustellen. LoginModule definiert fünf Methoden, die in einem Modul implementiert werden müssen. Die Anwendung, in der das Modul eingebunden wird, braucht diese Methoden nie direkt aufzurufen. Die gesamte Steuerung übernimmt der LoginContext. Die Methoden sind im Einzelnen:
initialize()In dieser Methode können grundlegende Initialisierungsarbeiten wie das Anlegen von benötigten Datenstrukturen erfolgen.
login()Diese Methode führt die erste Phase des Logins durch und ermittelt die Login-Informationen. Hierzu können eventuell erforderliche Benutzereingaben über die Callbacks abgefragt werden.
abort() und commit()Diese Methoden bilden die zweite Phase des Logins. In commit() müssen dem Subject ein oder mehrere Principal-Exemplare hinzugefügt werden. abort() dient in erster Linie zu »Aufräumarbeiten« wie dem Zurücksetzen von Datenelementen.

Schlägt das Login bei einer Modulkette fehl, wird abort() grundsätzlich aufgerufen, und zwar unabhängig davon, ob login() vorher ausgeführt wurde oder nicht.

logoutIn dieser Methode werden die vom Modul zugeteilen Principal-Exemplare wieder entzogen.
Ist die gesamte Kette erfolgreich ausgeführt worden, wird die Methode commit() aller Module ausgeführt. Beim Fehlschlagen der gesamten Authentifizierung wird die Methode abort() aller bis dahin aufgerufenen Module ausgeführt.

Nach der Implementierung kann das Modul über einen entsprechenden Eintrag in der JAAS-Konfigurationsdatei in eine Anwendung eingebunden werden, ohne diese neu zu übersetzen:
UserFileDemo {
  de.dpunkt.security.jaas.UserFileLoginModule required userfile=userdb;
};
Das folgende Beispiel implementiert ein Login-Modul, das eine Authentifizierung gegen eine Benutzerdatei realisiert. Die Benutzer melden sich dabei mit einem Benutzernamen und einem Passwort an. Dieses steht in gehashter Form neben dem Benutzernamen in der Datei:
  duke:9d97f24439634d793f0b61d2559ae5c734b98288
Die Datei wird dabei als Option in der Modulkonfiguration als Parameter userfile übergeben, der in der Methode initialize() ausgelesen wird. Alle anderen Parameter werden gespeichert. Darüber hinaus wird noch eine Hashtable initialisiert, in die die Dateieinträge eingelesen werden:
  public void initialize(Subject subject, CallbackHandler callbackHandler,
			 Map sharedState, Map options) {
    this.subject = subject;
    this.callbackHandler = callbackHandler;
    this.sharedState = sharedState;
    this.options = options;
    debug = "true".equalsIgnoreCase((String)options.get("debug"));
    userFileName = (String)options.get("userfile");
    userTable = new Hashtable();
  }

In der Methode login() wird die Datei in eine Hashtable eingelesen und die Eingaben abgefragt. Anschließend wird ein Hash über dem eingegebenen Passwort berechnet und mit dem Hash verglichen, der in der Benutzertabelle eingetragen ist:
     MessageDigest md;
     String pwdHash, calculatedPwdHashStr;
     byte[] calculatedPwdHash;
     try {
       md = MessageDigest.getInstance("SHA1");
     }
     catch(NoSuchAlgorithmException e) {
       throw new LoginException("no SHA-1 implementation found");
     }
     md.update(userID.getBytes());
     md.update(String.valueOf(password).getBytes());
     calculatedPwdHash = md.digest();
     for (int i = 0; i < password.length; i++)
       password[i] = ' ';
     password = null;
 
     pwdHash = (String)userTable.get(userID);
     if (pwdHash == null) {
       succeeded = false;
     } else {
       calculatedPwdHashStr = getDigestAsHexString(calculatedPwdHash);
       if (debug) {
 	System.out.println("\t\t[UserFileLoginModule]");
 	System.out.println("\t\t  hash from file : "+pwdHash);
 	System.out.println("\t\t  calculated hash: "+calculatedPwdHashStr);
       }
       succeeded = pwdHash.equals(calculatedPwdHashStr);
     }
Verläuft die gesamte Authentifizierung erfolgreich, wird dem Subject aus dem LoginContext ein Principal hinzugefügt:
       userFilePrincipal = new UserFilePrincipal(userID);
       if (!subject.getPrincipals().contains(userFilePrincipal)) {
 	subject.getPrincipals().add(userFilePrincipal);
         if (debug)
           System.out.println("\t\t[UserFileLoginModule] : added principal");
       }
In der Methode logout() wird dieses Principal entsprechend wieder entzogen:
  public boolean logout() throws LoginException {
    subject.getPrincipals().remove(userFilePrincipal);

    ...
    return true;
  }


Material zum Beispiel

Autorisierung

Seit J2SE 1.4 besteht die Möglichkeit, die Vergabe von Berechtigungen neben der Codebase oder einer Signatur auch an bestimmte Principals zu knüpfen. Das heißt, dass Berechtigungen für Zugriffe aus der Sandbox heraus nicht grundsätzlich erteilt werden, sobald der Code von einem bestimmten Ort stammt, sondern nur dann, wenn sich ein bestimmter Benutzer angemeldet hat, oder genauer gesagt, über bestimmte Principals verfügt.

Auf diese Weise kann die Zuteilung von Berechtigungen an Applets oder Applikationen, die unter einem SecurityManager laufen, noch weiter flexibilisiert werden. Voraussetzung für diese benutzerbezogene Zuteilung von Berechtigungen ist zunächst ein Login mit JAAS, so wie es im vorhergehenden Abschnitt beschrieben wurde.

Für den zweiten Schritt, den Zugriff aus der Sandbox heraus, verfügt die Klasse Subject über die Methode doAsPrivileged(). Dieser Methode müssen drei Argumente übergeben werden: Die folgende Abbildung zeigt die nötigen Schritte noch einmal im Überblick:

Abbildung 15.5: Modell der Authentifizierung und Autorisierung bei JAAS
Abbildung 15.5

Zunächst erfolgt eine Authentifizierung, d. h. ein Subject wird durch den Login-Prozess mit Principals versehen. Dieses Subject wird dann beim doPrivileged()-Aufruf angegeben. Die sicherheitskritische Operation löst schließlich Checks beim SecurityManager aus, die prüfen, ob die Policy die benötigten Berechtigungen für einen der Principals des Subjects gestattet.

Bevor die privilegierte Aktion durchgeführt werden kann, muss zunächst ein Login mit JAAS durchgeführt werden. Dieses Login erfolgt genauso wie im letzten Beispiel. Anschließend erfolgt der doAsPrivileged()-Aufruf.
  Subject subject = loginContext.getSubject();
  PrivilegedExceptionAction action = new FileAccessAction();
  try {
    subject.doAsPrivileged(subject, action, null);
  }
  catch(PrivilegedActionException e) {
    System.err.println("Caught exception "+e.getException());
  }
Diese Aktion versucht, eine Datei zu öffnen. In diesem Fall kann eine IOException ausgelöst werden. Da es sich hierbei um eine geprüfte Exception handelt, wird das Interface PrivilegedExceptionAction implementiert.
  class FileAccessAction implements PrivilegedExceptionAction {
  
    public Object run() throws IOException {
      new java.io.FileOutputStream("test.txt").close();
      return null;
    }
  
  }
Schließlich muss die Policy so definiert werden, dass die Aktion unter dem Principal gestattet ist. Darüber hinaus benötigt aber auch der Login-Vorgang und die privilegierte Ausführung selbst bestimmte Berechtigungen, wenn ein SecurityManager aktiv ist. Im Einzelnen sind in der Policy die folgenden Einträge nötig, die entweder global oder mit einer Codebase für die betreffenden Module vergeben werden können:
  // AuthorizationDemo: Setzen der Property
  permission java.util.PropertyPermission "java.security.auth.login.config",
                                          "read,write";
  // AuthorizationDemo: Erzeugen des Login-Kontexts
  permission javax.security.auth.AuthPermission
                                          "createLoginContext.UserFileDemo";
  // Login-Modul: Hinzufügen und Entfernen von Principals
  permission javax.security.auth.AuthPermission "modifyPrincipals";

  // AuthorizationDemo: Ausführen einer privilegierten Aktion
  permission javax.security.auth.AuthPermission "doAsPrivileged";
Weiterhin muss die benötigte Berechtigung für den entsprechenden Principal erteilt werden:
  grant Principal de.dpunkt.security.jaas.UserFilePrincipal "duke" {
    permission java.io.FilePermission "*", "write";
  };

Material zum Beispiel


 Inhaltsverzeichnis   Auf Ebene Zurück   Seite Zurück   Seite Vor   Auf Ebene Vor   Eine Ebene höher   Index

Copyright © 2002 dpunkt.Verlag, Heidelberg. Alle Rechte vorbehalten.