Beispiel: JBoss-spezifische Security
Inhalt:
Anlegen der Application
Anlegen des Projekts für das Login-Modul
Export und Import des Login-Modul-Projekts
Datenbank vorbereiten
Code und Config des Login-Moduls
EJB-Projekt
persistence.xml
Web-Projekt
Application Client-Projekt
Security-Domain für die ganze Anwendung
Ohne Annotations
Dieses Beispiel zeigt wie das Validieren eines Logins über ein eigenes Login-Modul
funktioniert, außerdem wird die J2EE-Security gezeigt.
Aufbau des Beispiels:
- Es gibt zwei Rollen in dieser Anwendung, "administrator" und "kunde".
- Die Benutzer werden in zwei Datenbanktabellen "ADMINISTRATOR" und "KUNDE"
abgelegt, die jeweils über die Spalten ID, NAME, LOGIN und PASSWORT verfügen.
- Die Implementation des Login-Moduls steckt in einem separaten Java-Projekt, das Login-Modul greift direkt
auf die Datenbanktabellen zu.
- Auf die Tabelle "KUNDE" greift eine gleichnamige EJB zu.
- Eine Session-Bean im EJB-Projekt enthält Methoden auf die nur Kunden bzw. Administratoren zugreifen dürfen.
- Zum Test dienen eine Webanwendung und ein Application-Client. Bei beiden muss der User sich anmelden, die Anmeldeinformationen
werden bis zum EJB-Projekt weitergeben.
Hier gibt es die Enterprise Application zum Download: Security.ear. Die Importanleitung findet
man im Stateless-Beispiel.
Das Login-Modul-Projekt muss separat heruntergeladen werden: SecurityLoginModule.zip
Die Import-Anleitung steht hier.
Wichtig: beim erstmaligen Import müssen zwei
Classpath Variables angelegt werden, siehe Anleitung für das Erzeugen des Projekts "SecurityLoginModule".
Anlegen der Application
Ein "EAR Application Project" mit dem Namen "Security" wird erstellt. Im zweiten Schritt müssen
wir an den Facets nichts ändern.
In Schritt 3 erstellen wir ein Web Module, Application Client Module und ein EJB Module.
Jetzt die Deployment-Deskriptoren "application.xml", "ejb-jar.xml", "web.xml" und "application-client.xml" auf JavaEE5 umstellen.
Anlegen des Projekts für das Login-Modul
JBoss bietet ein integriertes Login-Modul org.jboss.security.auth.spi.DatabaseServerLoginModule
,
das es erlaubt User direkt gegen zwei Datenbanktabellen zu authentifizieren: eine Tabelle liefert User und Passwort,
eine zweite Tabelle die Rollen der User. Ein Beispiel für die Verwendung dieses Login-Moduls
findet sich in der Datei "%JBOSS_HOME%\server\default\conf\login-config.xml".
Es ist leider für unser Beispiel nicht ausreichend da wir zwei Tabellen
für die User benötigen und die Rollen sich direkt aus der Tabelle ergeben in der der User gefunden
wird.
Um den Login gegen unsere Datenbanktabellen zu realisieren benötigen wir also ein eigenes Login-Modul.
Dieses könnten wir in oben erwähnter Config-Datei "login-config.xml" eintragen, sauberer ist es jedoch
es sauber in eine Zip-Datei zu verpacken die mit unserer Anwendung zusammen ausgeliefert wird.
Dies geschieht beim JBoss über "Service archives", kurz SAR (ganz normale Zip-Archive), die
Konfigurationen und Erweiterungen für den MBean-Mechanismus enthalten.
Das folgende Beispiel habe ich in Teilen aus dem JBoss-Guide (
http://docs.jboss.org/jbossas/jboss4guide/), Kapitel "8.5.4.2. A Custom LoginModule Example", gezogen.
Wir legen uns also ein neues Java-Projekt "SecurityLoginModule" an. Einzige Besonderheit: wir wünschen
uns unterschiedliche Ordner für Quellcode und class-Dateien.
Zuallererst müssen wir zwei JAR-Dateien des JBoss-Servers im Projekt referenzieren: "%JBOSS_HOME%\server\default\lib\jbosssx.jar"
und "%JBOSS_HOME%\client\jbossall-client.jar". Wir gehen in "Window" -> "Preferences" in den Zweig
"Java" -> "Build Path" -> "Classpath Variables". Hier fügen wir eine neue hinzu.
Sie bekommt den Namen "JBOSSSX", der Pfad zeigt auf "%JBOSS_HOME%\server\default\lib\jbosssx.jar".
Dito für eine zweite Variable "JBOSSALLCLIENT", die auf "%JBOSS_HOME%\client\jbossall-client.jar" verweist.
Dieser Verweis ist bei den J2EE-Projekten bereits über die JBoss-Serverdefinition enthalten, hier muss sie leider manuell
zugefügt werden.
Das Ergebnis sieht so aus:
Jetzt geht es in die Properties des Projekts. Unter "Java Build Path" -> "Libraries" klicken wir auf "Add Variable..". Im erscheinenden
Dialog "New Variable Classpath Entry" wählen wir die eben angelegten Variablen aus.
Das Ergebnis sieht so aus:
Export und Import des Login-Modul-Projekts
Export:
Das Login-Modul-Projekt kann nicht zusammen mit der EAR-Datei exportiert werden. Deshalb müssen wir es als "Archive file" exportieren
(im Dialog "Export" zu finden in der Rubrik "General").
Bei den zu exportierenden Elementen wählen wir das gesamte Projekt, ausgenommen bleibt nur das bin-Verzeichnis (da stecken nur
die class-Dateien drin). Wichtig ist dass die Option "Create only selected directories" gesetzt wird
und dass die Checkbox vor "SecurityLoginModule" nicht gesetzt ist!
Re-Import
Vor dem Re-Import müssen wir zuerst ein Java-Projekt "SecurityLoginModule" anlegen, idealerweise mit den gleichen Optionen
wie oben beschrieben.
Anschließend importieren wir ein "Archive file". Im Feld "Into Folder" geben wir den Namen des eben angelegten Projekts an.
Die Referenzen auf die beiden Classpath-Variablen werden ebenfalls importiert (enthalten in der Datei ".classpath").
Datenbank vorbereiten
In der Datenbank werden die benötigten Tabellen erzeugt und jeweils ein Testdatensatz
angelegt:
CREATE TABLE KUNDE
(ID int not null,
NAME varchar(100),
LOGIN varchar(100),
PASSWORT varchar(100) );
CREATE TABLE ADMINISTRATOR
(ID int not null,
NAME varchar(100),
LOGIN varchar(100),
PASSWORT varchar(100) );
insert into ADMINISTRATOR (ID, LOGIN, NAME, PASSWORT) values (1, 'ADMIN', 'Admin', 'ADMIN');
insert into KUNDE (ID, LOGIN, NAME, PASSWORT) values (1, 'DOOFI', 'DOOFI', 'DOOFI');
Zu beachten ist dass hier keine Primary Keys angelegt wurden, das sollte natürlich in der Realität nicht vorkommen !
Code und Config des Login-Moduls
Im Package de.fhw.swtvertiefung.knauf.security.loginmodule
werden drei Dateien zugefügt:
Administrator
ist ein simples Value Object für einen Administrator.
Kunde
ist ein simples Value Object für einen Kunden (hat aber nichts mit der später anzulegenden EJB zu tun).
KundeAdministratorLoginModule
ist die Implementation unseres Login-Moduls. Sie ist abgeleitet von
org.jboss.security.auth.spi.UsernamePasswordLoginModule
. Sie muss drei Methoden implementieren:
initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options)
: Initialisierung des Moduls. Hier wird eine DataSource (deren JNDI-Name konfigurierbar ist)
aus dem JNDI gezogen. Das Datenbankschema wird ebenfalls aus der Config geholt.
getUsersPassword()
: Ermitteln des Passworts für einen bestimmten User. Dieser wurde bereits (über die Basisklasse)
gesetzt und ist über die Methode super.getUsername()
abrufbar. Hier wird geprüft ob wir einen Kunden oder
einen Administrator vor uns haben, dessen Passwort wird zurückgegeben, bzw. eine javax.security.auth.login.LoginException
geworfen.
getRoleSets()
liefert alle Rollen des aktuellen Users (über super.getUsername()
ermittelbar)
zurück, hier entweder "kunde" oder "administrator".
Jetzt legen wir ein Verzeichnis "META-INF" im Projekt an (im Dialog "Select a wizard" zu finden unter "General" -> "Folder")
und fügen dort zwei Dateien zu:
jboss-service.xml ist die Kerndatei des Service Archives. Über sie initialisiert der Server das Modul. Wir deklarieren
hier wie unsere Config in den Server gelangen soll:
<?xml version="1.0" encoding="UTF-8"?>
<server>
<mbean code="org.jboss.security.auth.login.DynamicLoginConfig"
name="jboss:service=DynamicLoginConfig">
<attribute name="AuthConfig">META-INF/login-config.xml</attribute>
<depends optional-attribute-name="LoginConfigService">
jboss.security:service=XMLLoginConfig
</depends>
<depends optional-attribute-name="SecurityManagerService">
jboss.security:service=JaasSecurityManager
</depends>
</mbean>
</server>
Es wird eine MBean vom Typ org.jboss.security.auth.login.DynamicLoginConfig
deklariert, die wiederum eine andere
MBean Namens jboss.security:service=XMLLoginConfig
ansteuert die eine Login-Konfiguration aus der Datei "META-INF/login-config.xml"
lädt. Siehe JBoss-Wiki: http://wiki.jboss.org/wiki/Wiki.jsp?page=DynamicLoginConfig
und http://wiki.jboss.org/wiki/Wiki.jsp?page=LoginConfiguration.
Die MBean wird unter dem Service-Namen jboss:service=DynamicLoginConfig
registriert.
login-config.xml deklariert die Referenz auf unser Login-Modul.
<?xml version='1.0'?>
<!DOCTYPE policy PUBLIC
"-//JBoss//DTD JBOSS Security Config 3.0//EN"
"http://www.jboss.org/j2ee/dtd/security_config.dtd">
<policy>
<application-policy name = "knaufsecurity">
<authentication>
<login-module code = "de.fhw.swtvertiefung.knauf.security.loginmodule.KundeAdministratorLoginModule"
flag = "required">
<module-option name = "DATASOURCE_JNDI">java:/DefaultDS</module-option>
<module-option name = "DBSCHEMA">PUBLIC</module-option>
</login-module>
</authentication>
</application-policy>
</policy>
Wir vergeben eine "application-policy" für die dieses Login-Modul dient (diese wird später als "Security Domain" in
den einzelnen Projekten auftauchen). Als "login-module" dient unsere Klasse. Zwei Config-Parameter müssen als "module-option"
übergeben werden.
Leider hilft uns hier der WebTools-Plugin nicht beim Erzeugen der SAR-Datei, und auch das Kopieren zum Server muss
anders geschehen.
Deshalb bedienen wir uns des Build-Tools "Ant", dass in Eclipse integriert ist.
Wir legen im Projekt eine Datei "build.xml" mit diesem Inhalt an:
<!-- Build script für "securityloginmodule.sar"-->
<project name="SecurityLoginModule" default="securityloginmodule">
<property name="src.root" value="src"/>
<property name="meta-inf" value="META-INF"/>
<property name="build.dir" value="bin" />
<property name="build.classes.dir" value="${build.dir}" />
<target name="securityloginmodule">
<jar jarfile="${build.dir}/securityloginmodule.sar">
<metainf dir="${meta-inf}" includes="jboss-service.xml"/>
<metainf dir="${meta-inf}" includes="login-config.xml"/>
<fileset dir="${build.classes.dir}">
<include name="de/fhw/swtvertiefung/knauf/security/loginmodule/*"/>
</fileset>
</jar>
</target>
</project>
Es werden vier Variablen ("src.root", "meta-inf", "build.dir" und "build.classes.dir") deklariert, die Pfade
zu verschiedenen Teilen des Projekts deklarieren. Anschließend wird ein Build-Job "securityloginmodule" deklariert,
der eine JAR-Datei "securityloginmodule.sar" erzeugt und aus den Unteraufgaben "jboss-service.xml ins Verzeichnis meta-inf packen",
"login-config.xml ins Verzeichnis meta-inf packen" und "alle .class-Dateien packen" besteht.
Öffnen wir die Datei per Doppelklick, erscheint in der "Outline View" eine Baumstruktur der Datei, und per Rechtsklick können
wir die einzelnen Ant-Tasks aufrufen.
Jetzt können wir die erzeugte SAR-Datei (liegt im Verzeichnis "bin" des Projekts) in das Deploy-Verzeichnis des JBoss kopieren
(muss per Hand geschehen).
Rufen wir die JMX-Console auf, finden wir unsere DynamicLoginConfig-MBean hier:
In den Details finden wir unsere Angaben aus "jboss-service.xml" wieder:
EJB-Projekt
Jetzt wird das EJB-Projekt fertiggestellt.
Wir legen eine Entitiy-Bean "KundeBean" im Package "de.fhw.swtvertiefung.knauf.security" an, mit den Datenbankfeldern "ID", "LOGIN", "NAME" und "PASSWORT".
Der Code sieht so aus:
@Entity ()
@Table(name="KUNDE")
public class KundeBean implements Serializable
{
private static final long serialVersionUID = 1L;
private Integer intId;
private String strName;
private String strLogin;
private String strPasswort;
@Id
@Column(name="ID")
@GeneratedValue
public Integer getId()
{
return this.intId;
}
public void setId (Integer int_Id)
{
this.intId = int_Id;
}
@Column(name="NAME")
public String getName()
{
return this.strName;
}
public void setName (String str_Name)
{
this.strName = str_Name;
}
Column(name="LOGIN")
public String getLogin()
{
return this.strLogin;
}
public void setLogin (String str_Login)
{
this.strLogin = str_Login;
}
@Column(name="PASSWORT")
public String getPasswort()
{
return this.strPasswort;
}
public void setPasswort(String str_Passwort)
{
this.strPasswort = str_Passwort;
}
}
Wichtig ist dass hier ein korrektes Mapping auf den Namen und die Spalten der bereits vorhandenen Tabellen erfolgt !
Jetzt fügen wir eine Stateless Session Bean SecuredBean
im Package de.fhw.swtvertiefung.knauf.security
zu.
@Stateless
@DeclareRoles(value={"administrator", "kunde"})
@SecurityDomain(value="knaufsecurity")
public class SecuredBean implements Secured
{
@PersistenceContext(unitName="securityPersistenceUnit")
private EntityManager entityManager = null;
@RolesAllowed(value={"administrator"} )
public void forAdminOnly()
{
}
@RolesAllowed(value={"kunde"} )
public void forKundeOnly()
{
logger.info("forKundeOnly");
}
@RolesAllowed(value={"administrator", "kunde"} )
public void forBoth()
{
//Jetzt einen Datenbankzugriff versuchen:
Query query =this.entityManager.createQuery("select o from KundeBean o");
List<KundeBean> listKunden = query.getResultList();
Iterator<KundeBean> iteratorKunden = listKunden.iterator();
while (iteratorKunden.hasNext() == true)
{
KundeBean kundeAktuell = iteratorKunden.next();
logger.info("Kunde-Login: '" + kundeAktuell.getLogin() + "'");
}
}
}
Die Bean implementiert das Remote-Interface "Secured" das die Deklaration der drei Methoden enthält.
Die Methode "forBoth" liest alle Kunden ein und gibt diese aus (als Test ob unsere KundeBean korrekt deklariert ist ;-).
Um das Beispiel übersichtlicher zu halten wird die Query hier direkt angegeben, ohne sie als "NamedQuery" zu deklarieren.
Mittels der Annotation @DeclaredRoles
wird festgelegt welche Rollen in der Bean vorkommen. Da ich im
Beispiel zwei Rollen habe wird eine Array-Syntax verwendet. Das Beispiel hat übrigens auch ohne diese Annotation funktioniert.
Jede Methode enthält in einer Annotation @javax.annotation.security.RolesAllowed
ein String-Array der erlaubten Rollen.
Es gibt eine JBoss-spezifische Besonderheit: Über die Annotation @org.jboss.annotation.security.SecurityDomain
wird festgelegt dass für Zugriffe auf unsere Bean die Security Domain "java:/jaas/knaufsecurity" verwendet werden soll.
ACHTUNG: Es gibt eine gleichnamige Annotation "org.jboss.aspects.security.SecurityDomain",
diese dürfen wir nicht verwenden. Tun wir es doch ist unsere Bean absolut ungesichert, es gibt allerdings keine Fehlermeldungen.
ACHTUNG: Bei der SecurityDomain dürfen wir nicht wie im Web- oder Application-Projekt
die Security-Domain als "java:/jaas/knaufsecurity" angeben sondern nur als "knaufsecurity" !
persistence.xml
Für Entity Beans muss eine Persistence Unit deklariert werden. Dies geschieht über eine Datei "persistence.xml" im Unterverzeichnis
"META-INF" des EJB-Projekts.
Sie hat diesen Inhalt:
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
version="1.0">
<persistence-unit name="securityPersistenceUnit">
<jta-data-source>java:/DefaultDS</jta-data-source>
<properties>
<!-- Setzen dieser Property aktiviert das automatische Tabellen-Generieren und Löschen beim Deploy!
Hier darf das natürlich nicht passieren da unser Login-Modul die Tabellen benötigt !-->
<!-- <property name="hibernate.hbm2ddl.auto" value="create-drop" /> -->
<!-- SQL-Logging einschalten: -->
<property name="hibernate.show_sql" value="true"></property>
</properties>
</persistence-unit>
</persistence>
Wichtig in diesem Beispiel ist dass das automatische Erzeugen von Datenbanktabellen abgeschaltet wurde da
sonst unsere Tabelle "KUNDE" bei jedem Redeploy neu erzeugt würde.
Web-Projekt
Das Web-Projekt muss das EJB-Projekt referenzieren.
"web.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<display-name>SecurityWeb</display-name>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
<welcome-file>default.html</welcome-file>
<welcome-file>default.htm</welcome-file>
<welcome-file>default.jsp</welcome-file>
</welcome-file-list>
<security-constraint>
<display-name>Security Constraint für SecurityWeb</display-name>
<web-resource-collection>
<web-resource-name>Alles</web-resource-name>
<url-pattern>/*</url-pattern>
<http-method>GET</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<description>Administrator und Kunde dürfen auf diese Seite zugreifen</description>
<role-name>administrator</role-name>
<role-name>kunde</role-name>
</auth-constraint>
</security-constraint>
<login-config>
<auth-method>FORM</auth-method>
<form-login-config>
<form-login-page>/login.jsp</form-login-page>
<form-error-page>/error.jsp</form-error-page>
</form-login-config>
</login-config>
<security-role>
<description>Administrator im Web</description>
<role-name>administrator</role-name>
</security-role>
<security-role>
<description>Kunde im Web</description>
<role-name>kunde</role-name>
</security-role>
<ejb-ref>
<ejb-ref-name>ejb/Secured</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>java.lang.Object</home>
<remote>de.fhw.swtvertiefung.knauf.security.Secured</remote>
</ejb-ref>
</web-app>
Im Element "security-constraint" wird festgelegt welche Resourcen unserer Anwendung gesichert sein sollen. Ich
habe hier alles gesperrt.
"login-config" gibt an dass wir ein eigenes Login-Formular "login.jsp" verwenden wollen. Bei fehlgeschlagenen Logins soll eine
Fehlerseite "error.jsp" aufgerufen werden.
Schließlich werden die beiden Rollen deklariert die in der Anwendung vorkommen können. "role-name" verweist
Es werden die Security-Rollen deklariert die in der Webanwendung verwendet werden.
Zu dem Verweis auf die Secured-EJB muss nicht mehr gesagt werden.
"jboss-web.xml" enthält ebenfalls eine Neuerung (die Deklaration der Security Domain):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-web PUBLIC "-//JBoss//DTD Web Application 2.4//EN" "http://www.jboss.org/j2ee/dtd/jboss-web_4_0.dtd">
<jboss-web>
<security-domain>java:/jaas/knaufsecurity</security-domain>
<context-root>SecurityWeb</context-root>
<!-- EJB References -->
<ejb-ref>
<ejb-ref-name>ejb/Secured</ejb-ref-name>
<jndi-name>Security/SecuredBean/remote</jndi-name>
</ejb-ref>
</jboss-web>
Jetzt werden noch drei JSP-Dateien zugefügt: "index.jsp" ruft die drei Methoden der SecuredBean
auf.
"error.jsp" dient als Fehlerseite bei Anmeldefehlern. Und "login.jsp" ist unser Login-Formular und entspricht haargenau
dem WebSphere-Beispiel:
<form method="post" ACTION="j_security_check">
Login: <input type="text" name="j_username" /> <br>
Passwort: <input type="password" name="j_password" /> <br>
<input type="submit" name="login" value="Login">
</form>
Der Logout-Button in "index.jsp" muss hier zu einer selbstgebauten Seite führen da es im Gegensatz zum
WebSphere keine Standard-Logout-Aktion gibt.
<FORM METHOD=POST ACTION="logout.jsp" NAME="logout">
<input type="submit" name="logout" value="Logout">
</FORM>
Die Seite "logout.jsp" sieht im Kern so aus:
<%
//Session beenden:
session.invalidate();
%>
<P>Sie wurden abgemeldet ! </P>
<a href="index.jsp">Zurück zur Startseite</a>
Nach einem Deploy auf den Server sieht der Environment Naming Context der Webanwendung so aus:
Man erkennt die Security Domain.
Unsere schicke Anwendung ist jetzt unter http://localhost:8080/SecurityWeb
erreichbar und sollte uns mit dem Login-Formular beglücken.
Application Client-Projekt
Das Application Client Project muss das EJB-JAR referenzieren.
Die Klasse de.fhw.swtvertiefung.knauf.security.SecurityClient
wird zugefügt. In der Main-Methode geschehen
die Aufrufe der SecuredBean
.
In "application-client.xml" muss eine EJB-Referenz auf SecuredBean
eingetragen werden:
<?xml version="1.0" encoding="UTF-8"?>
<application-client id="Application-client_ID" version="5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/application-client_5.xsd">
<display-name>SecurityClient</display-name>
<ejb-ref>
<ejb-ref-name>ejb/Secured</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<home>java.lang.Object</home>
<remote>de.fhw.swtvertiefung.knauf.security.Secured</remote>
</ejb-ref>
</application-client>
Damit die EJB-Referenz funktioniert muss "jboss-client.xml" mit diesem Inhalt zugefügt werden:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-client PUBLIC "-//JBoss//DTD Application Client 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss-client_4_0.dtd" >
<jboss-client>
<jndi-name>SecurityClient</jndi-name>
<ejb-ref>
<ejb-ref-name>ejb/Secured</ejb-ref-name>
<jndi-name>Security/SecuredBean/remote</jndi-name>
</ejb-ref>
</jboss-client>
Das Kernproblem des ApplicationClient ist es, die Security-Informationen zu erfassen und zum Server zu bringen.
Zuerst einmal muss eine Anmeldekonfiguration angelegt werden. Dazu legen wir im Unterverzeichnis "appClientModule/META-INF"
eine Datei "auth.conf" mit diesem Inhalt an:
knaufclientsecurity {
// jBoss LoginModule
org.jboss.security.ClientLoginModule required
;
};
Mit dieser Datei wird festgelegt dass für die Anwendung eine SecurityDomäne "knaufclientsecurity"
existiert, als LoginModul dient org.jboss.security.ClientLoginModule
.
Eine Klasse muss implementiert werden die die Security-Informationen von der Client-Anwendung anfordert
und an den Server übergibt. Hierzu muss das Interface javax.security.auth.callback.CallbackHandler
implementiert werden. Dessen Methode handle (Callback[] callbacks)
wird vom Server aufgerufen,
das übergebene Array von Callback
s beschreibt die angeforderten Informationen (die in den
entsprechenden Callback geschrieben werden).
Der Code sieht ungefähr so aus (in der Implementation des Beispiels wird allerdings der Login vom User eingegeben):
public class SecurityClientCallbackHandler implements CallbackHandler
{
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException
{
//Über alle übergebenen Callbacks iterieren:
for (int intIndexCallback = 0; intIndexCallback < callbacks.length; intIndexCallback++)
{
//NameCallback: Login übergeben.
if (callbacks[intIndexCallback] instanceof NameCallback)
{
NameCallback nameCallback = (NameCallback) callbacks[intIndexCallback];
nameCallback.setName( "ADMIN" );
}
//PasswordCallback: Login übergeben.
else if (callbacks[intIndexCallback] instanceof PasswordCallback)
{
PasswordCallback passwordCallback = (PasswordCallback) callbacks[intIndexCallback];
passwordCallback.setPassword ("ADMIN".toCharArray() );
}
else
{
throw new UnsupportedCallbackException (callbacks[intIndexCallback], "Nicht unterstützer Callback !");
}
}
}
}
Die Vorbereitungen sind getroffen, jetzt können wir den Client erstellen. Die Initialisierung sieht so aus:
Properties props = new Properties();
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");
props.setProperty("j2ee.clientName", "SecurityClient");
InitialContext initialContext = new InitialContext(props);
//Login initialisieren:
SecurityClientCallbackHandler callbackHandler = new SecurityClientCallbackHandler();
LoginContext loginContext = new LoginContext ("knaufclientsecurity", callbackHandler);
loginContext.login();
Wichtig ist hier die Initialisierung des LoginContexts, dem eine Instanz unseres CallbackHandlers übergeben wird.
Als Name des LoginContexts muss der gleiche Wert angegeben sein der auch in "auth.conf" steckt !
Ab jetzt können wir die Beans aufrufen.
Beim Start des Clients ist zu beachten dass wir die Datei "auth.conf" in einem Java-Parameter
java.security.auth.login.config
übergeben müssen. Deshalb muss für den Client eine "Run Configuration"
angelegt werden.
Im Menü "Run" den Punkt "Run..." wählen. Links unter "Java Application" eine neue namens "SecurityClient" zufügen.
Auf der Karteikarte "Main" wird de.fhw.swtvertiefung.knauf.security.SecurityClient
als "Main class" gewählt.
Auf der Karteikarte "Arguments" wird das Argument -Djava.security.auth.login.config=appClientModule/META-INF/auth.conf
eingetragen:
Security-Domain für die ganze Anwendung
Alternativ zu der Deklaration der Security-Domain in Web- und EJB-Projekt könnten wir diese auch in einer Datei
"jboss-app.xml" im Projekt "Security" (also das Projekt aus dem die EAR-Datei erzeugt wird),
Unterverzeichnis "EarContent\META-INF" angeben. Die Datei könnte so aussehen:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss-app
PUBLIC "-//JBoss//DTD J2EE Application 1.4//EN"
"http://www.jboss.org/j2ee/dtd/jboss-app_4_0.dtd">
<jboss-app>
<security-domain>java:/jaas/knaufsecurity</security-domain>
</jboss-app>
Dies führt allerdings zu Problemen im EJB-Projekt da in der EJB3-Implementation die Security-Domain ohne den
Präfix "java:/jaas/" deklariert werden muss.
Deshalb ist dieser Ansatz für uns nicht verwendbar.
Eintrag in der JBoss-Bugdatenbank: http://jira.jboss.com/jira/browse/EJBTHREE-703
Ohne Annotations
Die Deklaration ohne Annotations erfordert eine ganze Reihe Arbeit.
ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar id="ejb-jar_ID" version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
<display-name>SecurityEJB</display-name>
<enterprise-beans>
<session>
<description>
<![CDATA[Die Methoden dieser Session-Bean sind für unterschiedliche Rollen zugelassen.]]>
</description>
<display-name>SecuredBean</display-name>
<ejb-name>SecuredBean</ejb-name>
<remote>de.fhw.swtvertiefung.knauf.security.Secured</remote>
<ejb-class>de.fhw.swtvertiefung.knauf.security.SecuredBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
<!--EntityManager-Injection -->
<persistence-context-ref>
<persistence-context-ref-name>
SecurityPersistenceUnitRef
</persistence-context-ref-name>
<persistence-unit-name>securityPersistenceUnit</persistence-unit-name>
<injection-target>
<injection-target-class>
de.fhw.swtvertiefung.knauf.security.SecuredBean
</injection-target-class>
<injection-target-name>entityManager</injection-target-name>
</injection-target>
</persistence-context-ref>
</session>
<!-- Entity Beans -->
<entity>
<description>
<![CDATA[Entity-Bean für einen Kunden. Greift intern auf die gleiche Tabelle zu wie das Security-Login-Modul. Tabellen- und Spaltennamen habe ich hier explizit deklariert da das Login-Modul mit den gleichen Namen arbeitet.]]>
</description>
<display-name>KundeBean</display-name>
<ejb-name>KundeBean</ejb-name>
<ejb-class>de.fhw.swtvertiefung.knauf.security.KundeBean</ejb-class>
<persistence-type>Container</persistence-type>
<prim-key-class>java.lang.Integer</prim-key-class>
<reentrant>false</reentrant>
</entity>
</enterprise-beans>
<assembly-descriptor>
<security-role>
<description>
<![CDATA[description not supported yet by ejbdoclet]]>
</description>
<role-name>kunde</role-name>
</security-role>
<security-role>
<description>
<![CDATA[description not supported yet by ejbdoclet]]>
</description>
<role-name>administrator</role-name>
</security-role>
<method-permission>
<role-name>administrator</role-name>
<method>
<description>
<![CDATA[Diese Methode darf nur vom Admin aufgerufen werden !]]>
</description>
<ejb-name>SecuredBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>forAdminOnly</method-name>
<method-params></method-params>
</method>
</method-permission>
<method-permission>
<role-name>kunde</role-name>
<method>
<description>
<![CDATA[Diese Methode darf nur vom Kunden aufgerufen werden !]]>
</description>
<ejb-name>SecuredBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>forKundeOnly</method-name>
<method-params></method-params>
</method>
</method-permission>
<method-permission>
<role-name>administrator</role-name>
<role-name>kunde</role-name>
<method>
<description>
<![CDATA[Diese Methode darf von Admin und Kunde aufgerufen werden !]]>
</description>
<ejb-name>SecuredBean</ejb-name>
<method-intf>Remote</method-intf>
<method-name>forBoth</method-name>
<method-params></method-params>
</method>
</method-permission>
</assembly-descriptor>
</ejb-jar>
Neu in diesem Beispiel:
- Deklaration der im Deployment Deskriptor verwendeten Rollen durch ein Element "security-role" (entspricht
der Annotation
@DeclaredRoles
in der Bean).
- Festlegen der Methodenberechtigungen durch Elemente "method-permission" (entspricht
der Annotation
@RolesAllowed
in der Bean). Hier muss man extrem vorsichtig
sein da bei einem Tippfehler z.B. im Bean-Namen keine Fehlermeldungen kommen.
"orm.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd"
version="1.0">
<entity class="de.fhw.swtvertiefung.knauf.security.KundeBean" access="PROPERTY"
metadata-complete="true">
<table name="KUNDE"></table>
<attributes>
<id name="id">
<column name="ID" />
<generated-value/>
</id>
<basic name="name">
<column name="NAME" />
</basic>
<basic name="login">
<column name="LOGIN" />
</basic>
<basic name="password">
<column name="PASSWORD" />
</basic>
</attributes>
</entity>
</entity-mappings>
Es sind keine Besonderheiten zu vorherigen Beispielen zu vermelden.
"jboss.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jboss PUBLIC "-//JBoss//DTD JBOSS 4.0//EN" "http://www.jboss.org/j2ee/dtd/jboss_4_0.dtd">
<jboss>
<security-domain>knaufsecurity</security-domain>
</jboss>
Wichtig ist hier die Deklaration "security-domain". Im Gegensatz zum Web-Projekt darf die hier NICHT den
Präfix "java:/jaas/" haben !
Die modifizierte Version des Projekts gibt es hier: SecurityNoAnnotation.ear.
ACHTUNG 1: Dieses Projekt kann nicht neben dem obigen Security-Beispiel existieren !
ACHTUNG 2: Ein Re-Import der EAR-Datei klappt nur wenn in ejb-jar.xml das Element "persistence-context-ref"
auskommentiert ist ! Ansonsten zeigt Eclipse eine Fehlermeldung, ohne sich allerdings über die Details auszulassen.
Dieses Element muss nach dem Import wieder einkommentiert werden !
Stand 28.01.2007
Historie:
11.10.2006: Erstellt (übernommen aus WS 2005 und angepaßt an EJB3, Loginmodul macht seine Datenbankverbindungen
jetzt sauberer zu)
27.10.2006: Annotation @DeclaredRoles
in die SessionBean zugefügt.
28.01.2007: Falsche EJB-Referenz in web.xml korrigiert.