Start - Publikationen - Wissen - TOGAF - Impressum -

Sicherheit über Eigenimplementierungen


Wer hat es nicht schon so oder ähnlich selber gemacht: ein Loginformular wird gegen eine DB-Tabelle verglichen, wenn

SELECT * FROM user WHERE name=? AND password=?
einen Treffer bringt, bekommt der Nutzer eine Session oder eine Credential-Instanz in seiner Session und ist ab diesen Zeitpunkt authentifiziert. Implementiert man diese Logik an zentraler Stelle in einem FrontController oder als ServletFilter kann diese Lösung sogar als akzeptabel angesehen werden. Dennoch muss man sich über die Nachteile dieses Vorgehens im klaren sein:
  • Der Code solcher Eigenentwicklungen ist nicht unbedingt leicht in anderen Anwendungen wiederverwendbar. Und ganz entscheidend: er wird per Anwendung deployed. Fachlich einsichtiger ist es jedoch, diese Logik für die gesamten Serverumgebung und alle Anwendungen bereitzustellen. Nutzer autorisieren sich dann nicht für eine Anwendung, sondern für eine gesamte Umgebung. Einmal authentifiziert können diese alle deployeten Anwendungen nutzen.
  • Dieses Argument lässt sich sogar erhärten - nehmen wir an in einem Unternehmen existiert schon eine Sicherheits-Infrastruktur gegen die sich Nutzer authentifizieren. Diese Credentials kann dann ein Server auswerten und als Authentifizierungsinformationen nutzen (Single Sign On).
  • Üblicherweise wird eine Anfrage auf eine nur für authentifizierte Benutzer erlaubte Ressource auf eine Loginseite redirected. Nach einem erfolgreichen Login soll wieder die ursprünglich angeforderte Seite aufgerufen werden, eine Logik, die man ausformulieren muss.

Der Rest des Artikels beschäftigt sich deshalb ausschließlich mit deklarativer Sicherheit als Alternative zu Eigenimplementierungen.

Einschränkung des Zugriffs auf Ressourcen - Autorisierung


Zunächst nehmen wir die Sicht der Anwendungsentwickler ein. Fachliche Logik ist hier auf der Grundlage von einigen wenigen Rollen designed, die allein die Anwendungsfälle der Anwendung betreffen. Der Zugriff auf Ressourcen kann dann in der web.xml auf diese Rollen eingeschränkt werden:

web.xml
:
<security-constraint>
  <web-resource-collection>
    <web-resource-name>users</web-resource-name>
    <url-pattern>/secureUser/*</url-pattern> // auch mehrere, dann UND-verknüpft
  </web-resource-collection>
  <auth-constraint>
    <role-name>user</role-name> // auch mehrere, dann UND-verknüpft
  </auth-constraint>
</security-constraint>

Im Beispiel sind alle Ressourcen unterhalb /secureUser nur für Nutzer der Rolle user erlaubt. Solche Constraints können noch wesentlich feingranularer formuliert werden, aber das Prinzip ist klar erkennbar.

Login - Authentifizierung


Als nächstes muss deklariert werden, wie ein Nutzer sich authentifizieren soll. Dazu wird in der web.xml eine der vier Methoden BASIC, FORM, DIGEST oder CLIENT-CERT angegeben. Bei DIGEST oder CLIENT-CERT authentifiziert sich der Nutzer, weil er im Besitz eines Zertifikats ist. Bei BASIC und FORM wird dem Benutzer ein Login-Screen präsentiert, der Nutzer authentifiziert sich, indem er sein Passwort kennt. Der Login-Screen wird bei BASIC vom Browser, bei FORM von der Anwendung geliefert, was mehr gestalterische Freiräume lässt:

web.xml - FORM basierte Authentifizierung
:
<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>myrealm</realm-name>
    <form-login-config>
      <form-login-page>/login.html</form-login-page>
      <form-error-page>/loginfailed.jsp</form-error-page>
    </form-login-config>
</login-config>

Will der Nutzer auf eine eingeschränkte Ressource zugreifen, so erkennt dies der Server und redirected den Client auf die angegebenen Loginseite, die bestimmten Namenskonventionen zu folgen hat:

login.html
:
<form method="POST" action="--[j_security_check]--">
 Name: <input type="text" name="--[j_username]--"><br>
 Passwort: <input type="password" name="--[j_password]--"><br>
 <input type="submit" name="send" value="login">
</form>

Gelingt die Authentifizierung nicht (das Passwort ist beispielsweise falsch), dann wird die Fehlerseite gezeigt und der Nutzer versucht es nochmal. Gelingt die Authentifizierung, dann wird automatisch erneut die ursprünglich angeforderte Ressource angefordert.

Bis hier sind keinerlei Annahmen über die Umgebung gemacht worden, in der die Anwendung laufen soll. Es ist lediglich deklariert, mit welchen Rollen auf welche Ressourcen zugegriffen werden darf, und wie sich ein Nutzer zu authentifizieren hat. Diese Anwendung ist damit portabel!

Security-Realms


Bisher ist nicht geklärt, wogegen bei der deklarativen Authentifizierung Name und Passwort geprüft werden. Das erfolgt serverspezifisch über sogenannte Security-Realms, also Implementierungen einer solchen Prüfung gegen ein nachgelagertes Repository. Viele Server bieten Implementierungen für Standard-Realms, die gegen das Filesystem, einen LDAP oder eine Datenbank gehen. Für Eigenimplementierungen muss man die Server-API konsultieren.

File-basierten Realms eignen sich ideal für die Entwicklungs- und Testphase, da man auf die Anbindung komplizierter Firmen-Legacy verzichten kann. Im Tomcat gibt es dafür die tomcat-user.xml. Im Glassfish können File-basiertes Realm über die Admin-Console verwaltet werden. Dazu in der Admin-Console Configuration - Security - Realms wählen:

Hier können nun Nutzer eingerichtet und Gruppen zugeordnet werden:

Ist ein Nutzer eingerichtet und einer Gruppe zugeordnet kann man sich endlich authentifizieren. Zu erkennen ist eine gelungene Authentifizierung daran, dass nun eine HTTP 403 - Fehlermeldung erscheint, anstatt die unter form-error-page adressierte Ressource:

Im Tomcat dient die tomcat-users.xml als File-basiertes Security-Realm:

Tomcat tomcat-users.xml - File-basiertes Realm
:
<tomcat-users>
  <role rolename="user"/>
  <role rolename="admin"/>
  :
  <user name="Fred" password="foobar" roles="user, admin"/>
  :
</tomcat-users>

Ein gutes Beispiel für eine JDBC Realm-Implementierung liefert der Tomcat, sie wird in der server.xml deklariert:

Tomcat server.xml - JDBC Realm
:
<Realm className="org.apache.catalina.realm.JDBCRealm" debug="99"
  driverName="org.gjt.mm.mysql.Driver"
  connectionURL="jdbc:mysql://localhost/authority"
  connectionName="test" connectionPassword="test"
  userTable="users" 
  userRoleTable="user_roles" 
  userNameCol="user_name" // in beiden Tabellen!
  userCredCol="user_pass"
  roleNameCol="role_name"
/>
Die create-Statements für die Datenbank (schematisch)
create table users (
  user_name         varchar(15) not null primary key,
  user_pass         varchar(15) not null
);
:
create table user_roles (
  user_name         varchar(15) not null,
  role_name         varchar(15) not null,
  primary key (user_name, role_name)
);

Mapping der Nutzer auf Rollen


Da immernoch nicht die angesprochene Ressource erreicht wird, fehlt noch etwas. Dazu muss man sich klarmachen: auf der einen Seite haben wir eine Anwendung, deren Logik mit fachliche Rollen designed ist und mit diesen Rollen werden die Ressourcen der Anwendung geschützt (in der web.xml). Auf der anderen Seite existiert ein Legacy-Repository beliebiger Art, gegen das sich ein Nutzer im Rahmen einer Realm-Implementierung authentifiziert. Ein praxisnahes Beispiel sind die in einem Firmen-LDAP gepflegten Mitarbeiter-Informationen. Hier sind die Nutzer wahrscheinlich Abteilungen und Gruppen zugeordnet, die die operativen Belange des Unternehmens wiederspiegeln und ansonsten nichts mit den Rollen der Anwendung gemein haben.

Das Verbindung beider Welten liegt in der Verantwortung des Deployers und erfolgt per Anwendung und ist serverspezifisch. Im Glassfish ist dazu die sun-web.xml mit entsprechenden Mapping-Informationen zu versehen. Mappen kann man Nutzer oder Gruppen der Realms zu Rollen der Anwendung:

z.B. sun-web.xml
:
<security-role-mapping>
  <role-name>admin</role-name>
  <principal-name>fred</principal-name>
</security-role-mapping>
<security-role-mapping>
  <role-name>user</role-name>
  <group-name>users</group-name>
</security-role-mapping>

Im Beispiel sind den beiden Rollen 'admin' und 'user' der Anwendung jeweils der Nutzer 'fred' und die Gruppe 'users' der Realm-Implementierung zugeordnet.

Die Tomcat Entwickler wählten einen anderen Weg. Im Tomcat werden die Rollen und Gruppen der Realms direkt als Rollen der Anwednung interpretiert. Damit nicht jede Anwendung die gleichen Rollen nutzen muss, kann für jede Anwendung eine eigene Realm-Implementierung deployed werden.

Geheime Übertragung der Logindaten - confidentiality


Mindestens die Übertragung des Passworts eines Login-Formulars muss verschlüsselt erfolgen. Dazu wird in der web.xml die Formularseite als CONFIDENTIAL deklariert, den Rest erledigt der Server:

web.xml
:
<security-constraint>
  <web-resource-collection>
   <web-resource-name>login</web-resource-name>
    <url-pattern>/login.html</url-pattern>
  </web-resource-collection>
  <user-data-constraint>
  <transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>

Praktisch alle Server setzen dabei auf SSL-Verschlüsselung. Für Entwicklung und Test wird dabei mit einem (wahrscheinlich on-the-fly) generierten Zertifikat gearbeitet - der Browser wird einen entsprechenden Warnhinweis geben. In produktiven Umgebungen werden Server mit signierten Zertifikaten betrieben.

Fazit


In Java EE sollte auf deklarative Authentifizierung und Autorisierung nicht verzichtet werden:

  • es vereinfacht Programmcode und erhöht die Sicherheit
  • der Server organisiert ein Handshake beim Betreten geschützter Seiten mit Redirect nach erfolgreicher oder misslungener Anmeldung
  • es erhöht die Portabilität der Anwendungen
  • es integriert nahtlos in die vorhande Sicherheitsarchitektur (Nutzung von Single Sign On etc.)
Damit deklarative Authentifizierung und Autorisierung funktioniert müssen drei Positionen berücksichtigt werden:
  1. Die Entwicklung baut eine Anwendung basierend auf fachliche Rollen und schützt rollenspezifisch Ressourcen der Anwendung. Dies und die Art der Authentifizierung (BASIC, FORM, DIGEST, CLIENT-CERT) wird in der web.xml deklariert und ist portabel.
  2. Server-Administratoren versorgen den Server mit einer serverspezifischen Realmimplementierung, gegen die die Authentifizieurung der Nutzer geht.
  3. Die Anwendungsdeployer mappen serverspezifisch die Nutzer und Gruppen der Realms zu den Rollen der Anwendung. In manchen Servern wird dieses Mapping indirekt (und etwas inkonsequent) über anwendungsspezifische Realms erledigt.

Literatur


The Java EE 5 Tutorial - Securing Web Applications

copyright © 2003-2021 | Dr. Christian Dürr | prozesse-und-systeme.de | all rights reserved