Benutzung eines Login Modules zur Authentifizierung unter JBoss 4.2.x Erstellung der Datenbank Tabellen Als Login Module soll das in der JBoss Installation bereits vorhandene DatabaseServerLoginModule verwendet werden. Dieses kann auf mehrere Datenbanktabellen zugreifen, die sowieso zur Persistenz von Entity Beans dienen. Das heißt, Entity Bean Einträge sollen zur Authentifizierung dienen. Auf Entity Beans kann in dieser Phase des Zugriffs auf den Application Server noch nicht direkt zugegriffen werden, da der Benutzer ja noch nicht authentifiziert ist. Daher muss ein direkter Zugriff auf die entsprechenden Datenbanktabellen geschehen. Grundsätzlich benötigt man dazu zwei Tabellen. Die Namen sind beliebig. Sie kennzeichnen die Benutzer und die mit ihnen verknüpften Rollen. Nennen wir sie hier User und Role (könnte auch Kunde und Rolle sein). Zwischen ihnen könnte eine ManyToMany-Beziehung bestehen, d.h. jeder User besitzt mindestens eine oder mehrere Rollen, eine Rolle kann von vielen Kunden eingenommen werden. User und Role müssen (minimal) die folgenden Felder enthalten (Typ ist nur als Beispiel zu sehen) User_Role definiert die n:m-beziehung: User: Feldname Typ Bedeutung id bigint Primärschlüssel loginname varchar(20) Login-Name password varchar(20) Passwort Role: Feldname Typ Bedeutung rid bigint Primärschlüssel rname varchar(20) Rollen-Name - 1 -
User_Role (Relationen-Tabelle): Feldname Typ Bedeutung user_id bigint Fremdschlüssel zu User role_id gigint Fremdschlüssel zu Role Für unser Projekt könnte die User-Tabelle die Kunden-Tabelle sein. Die Role-Tabelle entspricht einer weiteren Entity Bean. Beide besitzen eine n:m-verknüpfung. Der Rollen-Name entspricht den Security-Rollen in den Security- Annotationen der Session Beans. - 2 -
Definition einer JBoss Security Domain Wir ändern dazu die Datei login-config.xml im Unterverzeichnis server/default/conf des JBoss- Installationsverzeichnisses. Hier können wir den Eintrag Security domain for JBossMQ kopieren und wie folgt editieren: <!-- Security domain for Bugreport-System --> <application-policy name = "BugreportSecurity"> <authentication> <login-module code = "org.jboss.security.auth.spi.databaseserverloginmodule" flag = "required"> <module-option name = "unauthenticatedidentity"> gast </module-option> <module-option name = "dsjndiname"> java:/defaultds </module-option> <module-option name = "principalsquery"> SELECT PASSWORT FROM KUNDE WHERE LOGINNAME=? </module-option> <module-option name = "rolesquery"> SELECT R.ROLLE, 'Roles' FROM ROLLE AS R, KUNDE_ROLLE AS KR, KUNDE AS K WHERE K.LOGINNAME=? AND KR.ROLLE_ID = R.ID AND KR.BENUTZER_KNR = K.KUNDENNR </module-option> </login-module> </authentication> </application-policy> Die SQL Select-Befehle sind an die Gegebenheiten Ihrer Entity Beans und der daraus erzeugten Tabellen anzupassen. Der Name der application-policy ist natürlich frei wählbar. Der Datenbank JNDI- Name sollte wie gezeigt bleiben, damit die Tabellen in unserer Default- Datenbank gesucht werden. - 3 -
Einrichtung der Datenbank Die Datenbank muss in der richtigen Form existieren. Es muss unbedingt wenigstens ein Benutzer mit seiner Rolle (und deren Verknüpfung) eingetragen sein. Das heißt, es muss ein Benutzer mit einem Passwort und mit einer Rollenzugehörigkeit vorhanden sein, zu Anfang sinnvollerweise Loginname admin mit Passwort admin und Rolle admin. Das Eintragen ist mit dem Hypersonic-Utility HSQL Database Manager am einfachsten zu bewerkstelligen. Als Rollen kann man die beiden notwendigen, z.b. admin und kunde von Hand eintragen und dann unverändert lassen. Eine Datei mit entsprechendem SQL-Skript ist bei mehrmaligem Neuanlegen der Tabellen nützlich: insert into rolle (id, rolle) values (1, 'admin'); insert into rolle (id, rolle) values (2, 'kunde'); insert into rolle (id, rolle) values (3, 'entwickler'); insert into kunde (kundennr, name, version, loginname, passwort) values(1, 'Administrator', 0, 'admin', 'admin'); insert into kunde_rolle (kunden_nr, rolle_id) values(1, 1); insert into kunde (kundennr, name, version, loginname, passwort) values(2, 'Entwickler Joe', 0, 'joe', 'joe'); insert into kunde_rolle (kunden_nr, rolle_id) values(2, 3); commit; Auch hier ist das Skript natürlich an die Gegebenheiten Ihrer Datenbank anzugleichen. - 4 -
Sicherheitseinstellungen im Web-Deployment-Deskriptor In dem Deployment-Deskriptor web.xml der Web-Anwendung müssen jetzt die folgenden Änderungen vorgenommen werden: Security Constraint Zunächst muss ein Security Constraint mit einer Web Resource Collection angelegt werden, die den Zugriff auf bestimmte Pfade und Requesttypen des Webservers steuert. Fügen Sie den Text an das Ende der bisherigen web.xml Datei vor den <ejb-ref>-einträgen an. Hier ein Beispiel: <security-constraint> <display-name> BugreportSecurityConstraint </display-name> <web-resource-collection> <web-resource-name>sicherresource</web-resource-name> <url-pattern>/*</url-pattern> <http-method>get</http-method> <http-method>post</http-method> </web-resource-collection> <auth-constraint> <description></description> <role-name>admin</role-name> <role-name>kunde</role-name> <role-name>entwickler</role-name> </auth-constraint> </security-constraint> Die Namen <display-name> und <web-resource-name> sind frei wählbar. In der Web Resource Collection werden die betroffenen Requesttypen (üblich sind GET und POST) sowie den Pfad (hier /*, also alles unter dem Wurzelverzeichnis) der betroffenen Webressourcen (z.b. JSP- Seiten und URL-Pfade) eingestellt. Z.B. kann man so auch getrennte Sicherheitsbereiche für Administratoren und Kunden definieren. - 5 -
Unter <auth-constraint> werden die Rollen definiert, die Zugang zu den zu schützenden Ressourcen haben. Diese Rollen müssen mit den Tabelleneinträgen in der Role Tabelle übereinstimmen. Login Konfiguration Hier gibt man die Authentifizierungs-Methode (hier Form-based) und die Namen der Login-JSP und der JSP, die im fehlgeschlagenen Loginfall aufgerufen wird, an (hinter den obigen Text einfügen): <login-config> <auth-method>form</auth-method> <form-login-config> <form-login-page>/logon.jsp</form-login-page> <form-error-page>/logonerror.jsp</form-error-page> </form-login-config> </login-config> Die erforderlichen Dateien (logon.jsp und logonerror.jsp) finden Sie in dem Beispiel für ein Web-Projekt (CustomerWeb.war) auf der Internetseite der Lehrveranstaltung unter Praktikums-Aufgaben. In logon.jsp dürfen die Bezeichner j_security_check, j_username und j_password nicht verändert werden, da sie von dem Web-Server, der die formbasierte Authentifizierung durchführt, so erwartet. Dahinter werden noch die vorgesehenen Rollen definiert: <security-role> <role-name>admin</role-name> </security-role> <security-role> <role-name>kunde</role-name> </security-role> <security-role> <role-name>entwickler</role-name> </security-role> Security Domain Nun muss noch die oben definierte Security Domain dem Web-Projekt zugeordnet werden. In die Datei jboss-web.xml fügen wir als ersten Eintrag hinter dem öffnenden <jboss-web> Tag die Zeile - 6 -
<security-domain>java:/jaas/bugreportsecurity</security-domain> ein. Hierbei entspricht BugreportSecurity dem application-policy name in login-config.xml, den wir zu Anfang definiert haben. - 7 -
Security-Informationen in der Web- Schicht nutzen Nach der Servlet 2.4 Spezifikation gibt es 3 Methoden, um die Security-Informationen des Benutzers zuzugreifen. Alle sind in dem HttpServletRequest interface definiert: String user = request.getremoteuser() Liefert den Login-Namen des Benutzers. Boolean isuserinrole(string rolename) Liefert boolschen Wert, ob dem authentifizierte Benutzer die entsprechende Rolle zugeteilt wurde. Beispiel: if (request.isuserinrole("admin")) { // the user is in the manager role //... } java.security.principal getuserprincipal() Liefert den Namen des Benutzers, wie er im zugewiesenen Principal Objekt abgelegt ist. Dazu muss getname() des java.security.principal Objektes aufgerufen warden: Principal principal=request.getuserprincipal(); String username=principal.getname(); - 8 -
Session Beans bzw. ihre Methoden absichern Für jede zu schützende Session Bean muss die dafür zuständige Security Domain angegeben werden. Das ist eine JBoss-spezifische Erweiterung (Annotation org.jboss.annotation.security.securitydomain). Die notwendigen Rollen werden mit der Standardannotation @RolesAllowed angegeben. Mehrere Rollen können als Array von Strings angegeben werden: @RolesAllowed({"admin", "kunde"}). import javax.annotation.security.rolesallowed; import org.jboss.annotation.security.securitydomain; @Stateless @SecurityDomain("BugreportSecurity") @RolesAllowed("admin") // Auch mehrere möglich: @RolesAllowed({"admin", "entwickler"}) public class KundeSessionBean implements KundeSessionRemote {... Hier gilt der Schutz global für alle Methoden der Session Bean. Durch Annotationen direkt vor einzelnen Methoden kann dieser Default überschrieben werden. Eine weitere mögliche Annotation ist @PermitAll, um die Klasse oder die Methode ganz frei zu geben. - 9 -
Test Client authentifizieren Wenn die Security scharf geschaltet ist, müssen auch normale Application Clients und die Test Clients sich authentifizieren. Das klappt am besten, wenn man die TestCases nicht von dem JBossTestCase ableitet, sondern von junit.framework.testcase oder (noch besser) mit JUnit Version 4 arbeitet. Eclipse bietet dann sofort an, die entsprechende JUnit-Library in den Build-Pfad aufzunehmen, dem man zustimmen sollte. Mit aktivierter Security muss man nun eine andere InitialContext Factory benutzen. Login-Name und Passwort werden als SECURITY_PRINCIPAL bzw. SECURITY_CREDENTIALS angegeben.... Properties props = new Properties(); props.setproperty(context.security_principal, "admin"); props.setproperty(context.security_credentials, "admin"); props.setproperty(context.initial_context_factory, "org.jboss.security.jndi.jndilogininitialcontextfactory"); // Dies war der Wert ohne Security! //props.setproperty(context.initial_context_factory, "org.jnp.interfaces.namingcontextfactory"); props.setproperty(context.url_pkg_prefixes, "org.jboss.naming.client"); props.setproperty(context.provider_url, "jnp://localhost:1099"); // Diesen Namen anpassen! props.setproperty("j2ee.clientname", "BugreportTestClient"); try { Context ctx = new InitialContext(props); session = (KundeSessionRemote) ctx.lookup("java:comp/env/ejb/kundesessionremote");... } - 10 -