Beispiel: JBoss-spezifische Security


Inhalt:

Anlegen der Application
EJB-Projekt
persistence.xml
Datenbank vorbereiten
Konfiguration des Loginmoduls
Web-Projekt
Application Client-Projekt
Security-Domain für die ganze Anwendung
Alternative: Konfiguration über Service Archive
Export und Import des Service Archive-Projekts
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: Hier gibt es die Enterprise Application zum Download: Security.ear. Die Importanleitung findet man im Stateless-Beispiel.


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.
New application
Jetzt die Deployment-Deskriptoren "application.xml", "ejb-jar.xml", "web.xml" und "application-client.xml" auf JavaEE5 umstellen.

EJB-Projekt

Jetzt wird das EJB-Projekt fertiggestellt.
Wir legen eine Entitiy-Bean "BenutzerBean" im Package "de.fhw.swtvertiefung.knauf.security" an, mit den Datenbankfeldern "ID", "LOGIN", "NAME" und "PASSWORT".
Der Code sieht so aus:
@Entity ()
@Table(name="BENUTZER")
public class BenutzerBean implements Serializable
{
  private static final long serialVersionUID = 1L;

  private Integer intId;
  private String strLogin;
  private String strPasswort;
  
  private Collection<RolleBean> rollen;
  
  @Id
  @Column(name="ID")
  @GeneratedValue
  public Integer getId()
  {
    return this.intId;
  }
  
  public void setId (Integer int_Id)
  {
    this.intId = int_Id;
  }
  
  @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;
  }
  
  @ManyToMany()
  public Collection<RolleBean> getRollen()
  {
    return this.rollen;
  }
  
  public void setRollen (Collection<RolleBean> rollen)
  {
    this.rollen = rollen;
  }
}
 
Wichtig ist dass hier ein korrektes Mapping auf den Namen und die Spalten der bereits vorhandenen Tabellen erfolgt !
Die Klasse hat eine (unidirektionale) ManyToMany-Beziehung zur RolleBean. Da wir die Rollen per SQL anlegen und sie nirgends im Programm selbst verwendet werden müssen wir hier uns hier keine Gedanken über cascadeType oder fetchType machen.

Jetzt wird die RolleBean zugefügt:
@Entity ()
@Table(name="ROLLE")
public class RolleBean implements Serializable
{
  private static final long serialVersionUID = 1L;

  private Integer intId;
  private String strRolle;
  
 
  @Id
  @Column(name="ID")
  @GeneratedValue
  public Integer getId()
  {
    return this.intId;
  }
  
  public void setId (Integer int_Id)
  {
    this.intId = int_Id;
  }
  
  @Column(name="ROLLE")
  public String getRolle()
  {
    return this.strRolle;
  }
  
  public void setRolle(String str_Rolle)
  {
    this.strRolle = str_Rolle;
  }
} 


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 BenutzerBean o");
    List<BenutzerBean> listBenutzer = query.getResultList();
    Iterator<BenutzerBean> iteratorBenutzer = listBenutzer.iterator();
    while (iteratorBenutzer.hasNext() == true)
    {
      BenutzerBean benutzerAktuell = iteratorBenutzer.next();
      logger.info("Benutzer-Login: '" + benutzerAktuell.getLogin() + "'");
    }
  }
}
Die Bean implementiert das Remote-Interface "Secured" das die Deklaration der drei Methoden enthält. Die Methode "forBoth" liest alle Benutzer ein und gibt diese aus (als Test ob unsere BenutzerBean 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! -->
			<property name="hibernate.hbm2ddl.auto" value="create-drop" />
			<!-- SQL-Logging einschalten: -->
			<property name="hibernate.show_sql" value="true"></property>
		</properties>
	</persistence-unit>
</persistence>


Datenbank vorbereiten

In der Datenbank müssen nach jedem Deploy die Benutzer und die Rollen angelegt werden:
insert into rolle (id, rolle) values (1,  'administrator');
insert into rolle (id,  rolle) values (2,  'kunde');
insert into rolle (id,  rolle) values (3,  'gast');

insert into benutzer (id, login, passwort) values (1, 'admin', 'admin');
insert into benutzer (id, login, passwort) values (2, 'kunde', 'kunde');
insert into benutzer (id, login, passwort) values (3, 'gast', 'gast');

insert into benutzer_rolle (benutzer_id, rollen_id) values (1, 1);
insert into benutzer_rolle (benutzer_id, rollen_id) values (2, 2);
insert into benutzer_rolle (benutzer_id, rollen_id) values (3, 3);

Das ganze geht aber auch einfacher: JBoss sucht beim Deploy in der EJB-JAR-Datei nach einer Datei namens "import.sql" (auf der obersten Ebene, im Projekt also im Verzeichnis "ejbModule" abzulegen !) und führt diese aus.
Das führt zu dieser Ausgabe auf der Konsole:
22:54:52,371 INFO  [SchemaExport] Running hbm2ddl schema export
22:54:52,371 INFO  [SchemaExport] exporting generated schema to database
22:54:52,371 INFO  [SchemaExport] Executing import script: /import.sql
22:54:52,371 INFO  [SchemaExport] schema export complete 


Konfiguration des Loginmoduls

In die Datei "\server\default\conf\login-config.xml" wird die Security-Domain konfiguriert (verwendetes Login-Modul sowie dessen Konfiguration).
Dies sieht in unserem Fall so aus (der Eintrag wird vor dem schließenden "policy"-Element eingefügt):
<application-policy name="knaufsecurity">
	<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 BENUTZER WHERE LOGIN=?
			</module-option>
			<module-option name="rolesQuery">
				SELECT R.ROLLE, 'Roles' FROM ROLLE AS R, BENUTZER_ROLLE
				AS BR, BENUTZER AS B WHERE B.LOGIN=? AND BR.ROLLEN_ID
				= R.ID AND BR.BENUTZER_ID = B.ID
			</module-option>
		</login-module>
	</authentication>
</application-policy> 
Wichtig ist dass wir bei den Queries exakt arbeiten, denn Fehler dabei haben bei mir keinerlei Fehlermeldungen ergeben, der Login klappte einfach nicht.


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:
Environment Naming Context
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 Callbacks 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:
Run Application Client


Alternative: Konfiguration über Service Archive

Das Service-Archive-Projekt muss separat heruntergeladen werden: SecurityLoginModule.zip Die Import-Anleitung steht hier.

Statt die Konfiguration in oben erwähnter Config-Datei "login-config.xml" eintragen, können wir sie auch sauber in eine Zip-Datei 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.
Projekt "SecurityLoginModule"

Unserem Projekt werden zwei Konfigurationsdateien im Verzeichnis "META-INF" zugefügt:
jboss-service.xml enthält die JBoss-MBean-Konfigurationen:
<?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> 
Wir registrieren ein spezielles Login-Modul das dynamische Konfiguration erlaubt, die Konfiguration stammt aus der Datei "META-INF/login-config.xml", die so aussieht:
<?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="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 BENUTZER WHERE LOGIN=?
				</module-option>
				<module-option name="rolesQuery">
					SELECT R.ROLLE, 'Roles' FROM ROLLE AS R, BENUTZER_ROLLE
					AS BR, BENUTZER AS B WHERE B.LOGIN=? AND BR.ROLLEN_ID
					= R.ID AND BR.BENUTZER_ID = B.ID
				</module-option>
			</login-module>
		</authentication>
	</application-policy>
</policy> 

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"/>
        </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" und "login-config.xml ins Verzeichnis meta-inf 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.
Ant-Task

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:
DynamicLoginConfig-MBean (1)
In den Details finden wir unsere Angaben aus "jboss-service.xml" wieder:
DynamicLoginConfig-MBean (2)

Export und Import des Service Archive-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!
Export SecurityLoginModule

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.
Import SecurityLoginModule


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 Benutzer.]]>
			</description>
			<display-name>BenutzerBean</display-name>

			<ejb-name>BenutzerBean</ejb-name>

			<ejb-class>de.fhw.swtvertiefung.knauf.security.BenutzerBean</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[Rolle "Kunde"]]>
			</description>
			<role-name>kunde</role-name>
		</security-role>
		<security-role>
			<description>
				<![CDATA[Rolle "Administrator"]]>
			</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:

"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.BenutzerBean" access="PROPERTY"
	 metadata-complete="true">
		<table name="BENUTZER"></table>
		<attributes>
			<id name="id">
				<column name="ID" /> 
				<generated-value/>
			</id>
			<basic name="login">
				<column name="LOGIN" />
			</basic>
			<basic name="password">
				<column name="PASSWORD" />
			</basic>
			<many-to-many name="rollen" target-entity="de.fhw.swtvertiefung.knauf.security.RolleBean">
			</many-to-many>
		</attributes>
	</entity>
	
	<entity class="de.fhw.swtvertiefung.knauf.security.RolleBean" access="PROPERTY"
	 metadata-complete="true">
		<table name="ROLLE"></table>
		<attributes>
			<id name="id">
				<column name="ID" /> 
				<generated-value/>
			</id>
			<basic name="rolle">
				<column name="ROLLE" />
			</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 08.06.2007
Historie:
05.06.2007: Erstellt (übernommen aus WS 2006, kein eigenes Login-Modul mehr sondern JBoss-Standard-Modul)
08.06.2007: "import.sql" zugefügt.