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 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.
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!
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) );
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.
In Java EE sollte auf deklarative Authentifizierung und Autorisierung nicht verzichtet werden: