Beispiel: JBoss/WildFly-spezifische Security
Inhalt:
Grundlagen: WildFly 11 und Elytron
Anlegen der Application
EJB-Projekt
persistence.xml
Datenbank vorbereiten
Konfiguration der Security Domain
Logging der Security-Schicht
Web-Projekt
Application Client-Projekt
Security-Domain für die ganze Anwendung
Ohne Annotations
Dieses Beispiel zeigt die Verwendung der JavaEE-Security (JavaEE 7). Das Beispiel ist mit WildFly 26.1 entwickelt worden, der Code ist aber anwendbar ab WildFly 11.
Eine aktuelle Version für JakartaEE10 und WildFly 30 findet sich
hier.
Aufbau des Beispiels:
- Es gibt zwei Rollen in dieser Anwendung, "administrator" und "kunde".
- Die Benutzer werden in einer Datenbanktabelle "BENUTZER"
abgelegt, die über die Spalten ID, LOGIN und PASSWORT verfügen.
- Auf die Tabelle "BENUTZER" 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. Bei WildFly 24 und neuer müssen im ApplicationClient-Projekt drei JAR-Dateien zum ClassPath zugefügt werden, siehe hier.
Und hier sind die Scripte, um die Server-Konfiguration anzupassen (configure.cli) bzw. sie wieder zurücksetzen (restore-configuration.cli).
Um sie auszuführen:
jboss-cli.bat --file=pfad_zu\configure.cli
Grundlagen: WildFly 11 und Elytron
Ab WildFly 11 bildet Elytron die Sicherheitsschicht:
(https://community.jboss.org/wiki/WildFlyElytron-ProjectSummary).
Dadurch änderte sich die Konfiguration der Security-Schicht komplett. Dieses Beispiel ist deshalb nicht auf früheren Versionen lauffähig!
Unter ../security/index.html findet sich eine Version des Beispiels für JBoss 7 bis WildFly 10.
Doku von Elytron: https://docs.wildfly.org/19/WildFly_Elytron_Security.html
Anlegen der Application
Ein leeres "EAR Application Project" mit dem Namen "Security" erstellen. In Variante 1 werden keine Deployment-Deskriptoren benötigt (außer "web.xml").
Zu erzeugende Module zufügen. Dieses Beispiel benötigt ein EJB-, ein Application Client- und ein Webprojekt.
EJB-Projekt
Wir legen eine Entitiy-Bean "BenutzerBean" im Package de.fhw.komponentenarchitekturen.knauf.security
an, mit den Datenbankfeldern "ID", "LOGIN", "NAME" und "PASSWORT".
Hier ist es wichtig, die Tabellen- und Spaltennamen explizit zu deklarieren, da wir diese Spaltennamen im Login-Modul benutzen müssen,
und uns deshalb nicht auf die Generierungsregeln des JBoss verlassen sollten!
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()
@JoinTable (name="BENUTZER_ROLLE",
joinColumns={@JoinColumn(name="BENUTZER_ID") },
inverseJoinColumns={@JoinColumn(name="ROLLEN_ID") })
public Collection<RolleBean> getRollen()
{
return this.rollen;
}
public void setRollen (Collection<RolleBean> rollen)
{
this.rollen = rollen;
}
}
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.
Wichtig ist bei der Relationship, dass wir hier den Namen der Relationstabelle sowie deren Spalten explizit über die Annotation @JoinTable
definieren.
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
{
private static final Logger logger = Logger.getLogger (SecuredBean.class.getName() );
@PersistenceContext(unitName="securityPersistenceUnit")
private EntityManager entityManager = null;
@Resource()
private SessionContext sessionContext = null;
@RolesAllowed(value={"administrator"} )
public void forAdminOnly()
{
logger.info("forAdminOnly");
}
@RolesAllowed(value={"kunde"} )
public void forKundeOnly()
{
logger.info("forKundeOnly");
}
@RolesAllowed(value={"administrator", "kunde"} )
public void forBoth()
{
logger.info("forBoth");
logger.info ("Caller: '" + this.sessionContext.getCallerPrincipal().getName() +"'");
logger.info ("Caller in Rolle 'kunde' ? " + this.sessionContext.isCallerInRole("kunde"));
logger.info ("Caller in Rolle 'administrator' ? " + this.sessionContext.isCallerInRole("administrator"));
//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 "SecuredRemote", 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.ejb3.annotation.SecurityDomain
wird festgelegt dass für Zugriffe auf unsere Bean die Security Domain "knaufsecurity" verwendet werden soll.
ACHTUNG:
Es gibt eine gleichnamige Annotation "org.jboss.security.annotation.SecurityDomain",
diese dürfen wir nicht verwenden. Tun wir es doch ist unsere Bean absolut ungesichert, es gibt beim Deploy allerdings keine Fehlermeldungen.
Nur zur Laufzeit erhalten wir eine Meldung "JBAS013323: Invalid User"
Um in einer SessionBean Informationen über den aktuellen Benutzer zu erhalten, muss man auf den javax.ejb.SessionContext
zugreifen. Diesen kann man sich mittels der Annotation @javax.annotation.Resource
injizieren lassen. Die Methode getCallerPrincipal()
liefert Informationen über den User. Mittels isCallerInRole(rolle)
kann man abfragen, ob der User eine bestimmte Rolle hat.
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://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
version="2.1">
<persistence-unit name="securityPersistenceUnit">
<jta-data-source>java:jboss/datasources/ExampleDS</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 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 (id, login, passwort) values (4, 'both', 'both');
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 (4, 1);
insert into benutzer_rolle (benutzer_id, rollen_id) values (4, 2);
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.
Beim Deploy wird diese Meldung ausgegeben:
2017-09-24 20:39:59,018 INFO [org.hibernate.tool.schema.internal.SchemaCreatorImpl] (ServerService Thread Pool -- 63) HHH000476: Executing import script 'ScriptSourceInputFromUrl(vfs:/C:/Temp/wildfly-11.0.0.CR1/bin/content/Security.ear/SecurityEJB.jar/import.sql)'
Konfiguration der Security Domain
Die Konfiguration der Security erfolgt über die Server-Konfiguration in "standalone.xml" (bzw. "domain.xml").
Quelle für diese Konfiguration: Die Konfiguration eines Datenbank-Login wurde dem WildFly-Quickstart Servlet-Security entnommen.
Die Konfiguration für den Application Client wurde dem WildFly-Quickstart EJB-Securit per JAAS entnommen.
Schritt 1:
Im Abschnitt <subsystem xmlns="urn:wildfly:elytron:1.0">
wird unter security-realms
ein jdbc-realm
definiert:
<security-realms>
...
<jdbc-realm name="SecurityBeispielDbRealm">
<principal-query sql="SELECT PASSWORT FROM BENUTZER WHERE LOGIN = ?" data-source="ExampleDS">
<clear-password-mapper password-index="1"/>
</principal-query>
<principal-query sql="SELECT R.ROLLE 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" data-source="ExampleDS">
<attribute-mapping>
<attribute to="roles" index="1"/>
</attribute-mapping>
</principal-query>
</jdbc-realm>
...
</security-realms>
Das JDBC-Realm verifizieren Benutzername und Passwort gegen Datenbanktabellen. Diese Datenbanktabellen entsprechen unseren EJBs.
Jedes Security Realm erfordert eine eigene Konfiguration. Für das JDBC-Realm sind dies ein oder mehrere Queries, die für das Validieren eines Passworts und für das Zuordnen von Benutzerrollen zum Login dienen:
Es werden hier zwei Queries definiert:
- Die erste Query wird genutzt, um zu einem eingegebenen Login das Passwort zu ermitteln. WildFly prüft dann, ob das eingegebene Passwort mit dem aus der Datenbank geladenen Passwort übereinstimmt.
Der Query wird gegen die im Attribut data-source
angegebene Datenbank ausgeführt, hier also gegen die in WildFly integrierte Datenbank.
Das Unterelement clear-password-mapper
liest das Passwort aus der Query: das Passwort wird am Index 1 des ResultSet gesucht und liegt im Klartext vor.
- Die zweite Query wird genutzt, um zu einem Login die Rollen zu ermitteln: sie selektiert eine Liste von Rollen.
Das Unterelement attribute-mapping
definiert in seinem Unterelement attribute
, aus welcher Spalte der Query die Rollen geholt werden (index="1"
), und scheinbar
werden sie durch to="roles"
in eine Variable "roles" geschrieben. Diese Variable könnte man auch beliebig anders benennen, man muss nur den "mapper" anpassen, siehe nächster Schritt.
Schritt 2:
Im Abschnitt <subsystem xmlns="urn:wildfly:elytron:1.0">
muss unter mappers
ein Role-Mapper definiert werden.
Dieser sorgt dafür, dass aus einem Attribut "roles" (das in der "principal-query" aus Schritt 1 als Attribut der ersten Spalte geliefert wird) eine Rollenzuordnung macht.
<mappers>
...
<simple-role-decoder name="from-roles-attribute" attribute="roles"/>
...
</mappers>
Schritt 3:
Im Abschnitt <subsystem xmlns="urn:wildfly:elytron:1.0">
muss unter security-domains
eine Security Domain definiert werden:
<security-domains>
...
<security-domain name="SecurityBeispielDomain" default-realm="SecurityBeispielDbRealm" permission-mapper="default-permission-mapper">
<realm name="SecurityBeispielDbRealm" role-decoder="from-roles-attribute"/>
</security-domain>
...
</security-domains>
Diese verwendet das JDBC-Realm aus Schritt 1 und ordnet dem Realm den Role-Decoder aus Schritt 2 zu, der sich wiederum darum kümmert, dass die selektierten Rollennamen korrekt verarbeitet werden.
Schritt 4:
Im Abschnitt <subsystem xmlns="urn:wildfly:elytron:1.0">
muss unter http
ein HTTP-Authentifizierungsverfahren angelegt werden:
<http>
...
<http-authentication-factory name="security-beispiel-http-authentication" http-server-mechanism-factory="global" security-domain="SecurityBeispielDomain">
<mechanism-configuration>
<mechanism mechanism-name="FORM">
<mechanism-realm realm-name="SecurityBeispiel Realm"/>
</mechanism>
</mechanism-configuration>
</http-authentication-factory>
...
</http>
Diese referenziert die Security Domain aus Schritt 3 und definiert einen Mechanismus "FORM" - dies bedeutet, dass unsere Webanwendung Form-Authentifizierung verwendet. Der Default wäre "PLAIN"-Authentifizierung.
Anmerkung: Das Attribut "mechanism-realm" hat scheinbar keine weitere Bedeutung.
Schritt 5:
Es wird die Sicherheit für die EJB-Schicht konfiguriert.
Im Abschnitt <subsystem xmlns="urn:jboss:domain:ejb3:5.0">
wird unter application-security-domains
eine application-security-domain
definiert.
Das Element application-security-domains
ist in der Default-Konfiguration nicht enthalten (weil es leer wäre), deshalb muss man es eventuell hinzufügen.
<application-security-domains>
...
<application-security-domain name="security-beispiel-domain" security-domain="SecurityBeispielDomain"/>
...
</application-security-domains>
Der Name der Security Domain (name="security-beispiel-domain"
) wird im EJB- und Web-Projekt verwendet.
Schritt 6:
Jetzt geht es in die Konfiguration der Webschicht: dieser Abschnitt ist nur nötig, wenn eine Webanwendung mit Security verwendet werden soll.
Im Abschnitt <subsystem xmlns="urn:jboss:domain:undertow:4.0">
muss unter application-security-domains
eine application-security-domain
definiert werden.
Das Element application-security-domains
ist in der Default-Konfiguration nicht enthalten (weil es leer wäre), deshalb muss man es eventuell ebenfalls hinzufügen.
<application-security-domains>
...
<application-security-domain name="security-beispiel-domain" http-authentication-factory="security-beispiel-http-authentication"/>
...
</application-security-domains>
Auch hier gilt: Der Name der Security Domain (name="security-beispiel-domain"
) wird im EJB- und Web-Projekt verwendet. Sie refewrenziert die in Schritt 4 definierte http-authentication-factory
Schritt 7:
In den letzten beiden Schritten wird die Security für Remote-Zugriffe konfiguriert.
Im Abschnitt <subsystem xmlns="urn:wildfly:elytron:1.0">
wird unter sasl
eine sasl-authentication-factory
definiert:
<sasl>
...
<sasl-authentication-factory name="security-beispiel-sasl-authentication" sasl-server-factory="configured" security-domain="SecurityBeispielDomain">
<mechanism-configuration>
<mechanism mechanism-name="PLAIN"/>
</mechanism-configuration>
</sasl-authentication-factory>
...
</sasl>
Hier wird wiederum unsere Security Domain referenziert. Außerdem wird als Mechanismus "PLAIN" definiert: Clients senden ihre Login-Daten also ohne Verschlüsselung.
"SASL" steht für "Simple Authentication and Security Layer".
Schritt 8:
Im Abschnitt <subsystem xmlns="urn:jboss:domain:remoting:4.0">
wird der http-connector
namens "http-remoting-connector" modifiziert: es wird ein Attribut "sasl-authentication-factory"
zugefügt, das auf die Authentication Factory aus Schritt 7 verweist:
<subsystem xmlns="urn:jboss:domain:remoting:4.0">
<endpoint/>
<http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm" sasl-authentication-factory="security-beispiel-sasl-authentication"/>
</subsystem>
ACHTUNG: dies bewirkt, dass alle Remote-Client-Anmeldungen jetzt über diese Authentication Factory laufen, d.h. es ist wohl nicht mehr möglich, anonymen EJB-Zugriff über andere Clients zu machen.
Das folgende (professionell erstellte ;-)) Bild stellt dar, wie die einzelnen Komponente ineinander greifen:
Security Domain über CLI anlegen
Hierzu ist folgende Sequenz von Befehlen nötig (bei längeren Befehlen habe ich der Lesbarkeit zuliebe Zeilumbrüche
eingefügt, die natürlich entfernt werden müssen).
Hinweis: die Ausgabe ist entweder
{
"outcome" => "success",
"response-headers" => {
"operation-requires-reload" => true,
"process-state" => "reload-required"
}
}
oder
{"outcome" => "success"}
Im ersteren Fall muss man in der CLI anschließend (bzw. nach Ausführung der ganzen Sequenz) ein "reload" aufrufen:
[standalone@localhost:9990 /] reload
Schritt 1:
[standalone@localhost:9990 /] /subsystem=elytron/jdbc-realm=SecurityBeispielDbRealm:add
(
principal-query=[
{sql="SELECT PASSWORT FROM BENUTZER WHERE LOGIN = ?",
data-source=ExampleDS,
clear-password-mapper={password-index=1}
},
{sql="SELECT R.ROLLE 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",
data-source="ExampleDS",
attribute-mapping=[{to="roles", index="1"}]
}
]
)
Schritt 2:
[standalone@localhost:9990 /] /subsystem=elytron/simple-role-decoder="from-roles-attribute":add(attribute="roles")
Schritt 3:
[standalone@localhost:9990 /] /subsystem=elytron/security-domain=SecurityBeispielDomain:add
(
default-realm=SecurityBeispielDbRealm,
permission-mapper=default-permission-mapper,
realms=[
{realm=SecurityBeispielDbRealm2,
role-decoder=from-roles-attribute
}
]
)
Schritt 4:
[standalone@localhost:9990 /] /subsystem=elytron/http-authentication-factory=security-beispiel-http-authentication:add
(
http-server-mechanism-factory=global,
security-domain=SecurityBeispielDomain,
mechanism-configurations=[
{mechanism-name=FORM,
mechanism-realm-configurations=[{realm-name="SecurityBeispiel Realm"}]
}
]
)
Schritt 5:
[standalone@localhost:9990 /] /subsystem=ejb3/application-security-domain=security-beispiel-domain:add(security-domain=SecurityBeispielDomain)
Schritt 6:
[standalone@localhost:9990 /] /subsystem=undertow/application-security-domain=security-beispiel-domain:add(http-authentication-factory=security-beispiel-http-authentication)
Schritt 7:
[standalone@localhost:9990 /] /subsystem=elytron/sasl-authentication-factory=security-beispiel-sasl-authentication:add
(
sasl-server-factory=configured,
security-domain=SecurityBeispielDomain,
mechanism-configurations=[{mechanism-name=PLAIN}]
)
Schritt 8:
[standalone@localhost:9990 /] /subsystem=remoting/http-connector=http-remoting-connector:write-attribute(name=sasl-authentication-factory,value=security-beispiel-sasl-authentication)
Um die Konfiguration wieder loszuwerden:
[standalone@localhost:9990 /] /subsystem=remoting/http-connector=http-remoting-connector:undefine-attribute(name=sasl-authentication-factory)
[standalone@localhost:9990 /] /subsystem=elytron/sasl-authentication-factory=security-beispiel-sasl-authentication:remove
[standalone@localhost:9990 /] /subsystem=undertow/application-security-domain=security-beispiel-domain:remove
[standalone@localhost:9990 /] /subsystem=ejb3/application-security-domain=security-beispiel-domain:remove
[standalone@localhost:9990 /] /subsystem=elytron/http-authentication-factory=security-beispiel-http-authentication:remove
[standalone@localhost:9990 /] /subsystem=elytron/security-domain=SecurityBeispielDomain:add
[standalone@localhost:9990 /] /subsystem=elytron/simple-role-decoder=from-roles-attribute:remove
[standalone@localhost:9990 /] /subsystem=elytron/jdbc-realm=SecurityBeispielDbRealm:remove
[standalone@localhost:9990 /] reload
Diese Schritte kam man sich auch in einer Scriptdatei speichern. Sie wird ausgeführt über den Befehl "jboss-cli.bat --file=pfad_zum_script\meinscript.cli"
Wichtig: als erster Befehl muss "connect" in der Scriptdatei stehen, oder man muss "jboss-cli.bat" mit dem Befehl "--connect" aufrufen.
Die Scripte sind zu Anfang dieser Seite verlinkt.
Logging der Security-Schicht
Fehler in den Queries des Login-Moduls (oder sonstige Konfigurationsfehler) werden uns leider nicht in der Konsole angezeigt. Falls der Login ohne erkennbare Ursache fehlschlägt, muss man
das Logging für die Security-Schicht einschalten. Das Vorgehen ist ähnlich wie das Vorgehen beim Aktivieren des SQL-Parameter-Loggings:
In "%WILDFY_HOME\standalone\configuration\standalone.xml" sucht man den Bereich subsystem xmlns="urn:jboss:domain:logging:3.0". Hier wird ein
neuer ConsoleHandler deklariert, der auf dem Level "Trace" steht:
<console-handler name="CONSOLE.SECURITY">
<level name="TRACE"/>
<formatter>
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%E%n"/>
</formatter>
</console-handler>
Alle Ausgaben der Packages org.jboss.security
und org.wildfly.security
sollen auf diesen Appender loggen:
<logger category="org.jboss.security">
<level name="TRACE"/>
<handlers>
<handler name="CONSOLE.SECURITY"/>
</handlers>
</logger>
<logger category="org.wildfly.security">
<level name="TRACE"/>
<handlers>
<handler name="CONSOLE.SECURITY"/>
</handlers>
</logger>
Das Log enthält jetzt leider sehr viele Ausgaben, aber dafür auch die Queries des Login-Moduls, und vor allem eventuell intern auftretende Exceptions.
Beispielausgabe für den Login des Users "admin":
2017-09-28 19:56:55,360 DEBUG [org.wildfly.security] (default task-2) Using UsernamePasswordAuthenticationMechanism for username authentication. Realm: [null], Username: [admin].
2017-09-28 19:56:55,361 TRACE [org.wildfly.security] (default task-2) Handling NameCallback: authenticationName = admin
2017-09-28 19:56:55,361 TRACE [org.wildfly.security] (default task-2) Principal assigning: [admin], pre-realm rewritten: [admin], realm name: [SecurityBeispielDbRealm], post-realm rewritten: [admin], realm rewritten: [admin]
2017-09-28 19:56:55,365 TRACE [org.wildfly.security] (default task-2) Executing principalQuery SELECT PASSWORT FROM BENUTZER WHERE LOGIN = ? with value admin
2017-09-28 19:56:55,372 TRACE [org.wildfly.security] (default task-2) Executing principalQuery SELECT R.ROLLE 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 with value admin
2017-09-28 19:56:55,375 TRACE [org.wildfly.security] (default task-2) Executing principalQuery SELECT PASSWORT FROM BENUTZER WHERE LOGIN = ? with value admin
2017-09-28 19:56:55,382 TRACE [org.wildfly.security] (default task-2) Authorizing username: [admin], Request URI: [http://localhost:8080/SecurityWeb/j_security_check], Context path: [/SecurityWeb]
2017-09-28 19:56:55,384 TRACE [org.wildfly.security] (default task-2) Role mapping: principal [admin] -> decoded roles [administrator] -> realm mapped roles [administrator] -> domain mapped roles [administrator]
2017-09-28 19:56:55,384 TRACE [org.wildfly.security] (default task-2) Authorizing principal admin.
2017-09-28 19:56:55,384 TRACE [org.wildfly.security] (default task-2) Authorizing against the following attributes: [roles] => [administrator]
2017-09-28 19:56:55,385 TRACE [org.wildfly.security] (default task-2) Permission mapping: identity [admin] with roles [administrator] implies ("org.wildfly.security.auth.permission.LoginPermission" "") = true
2017-09-28 19:56:55,386 TRACE [org.wildfly.security] (default task-2) Authorization succeed
Web-Projekt
Das Web-Projekt muss das EJB-Projekt referenzieren.
"web.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1">
<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>
<remote>de.fhw.komponentenarchitekturen.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 auf die Rollennamen,
die in der Annotation @RolesAllowed
der EJB-Methoden definiert sind.
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"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/schema/jbossas/jboss-web_12_1.xsd"
version="12.1">
<context-root>SecurityWeb</context-root>
<security-domain>knaufsecurity</security-domain>
<!-- EJB References -->
<ejb-ref>
<ejb-ref-name>ejb/Secured</ejb-ref-name>
<jndi-name>java:global/Security/SecurityEJB/SecuredBean!de.fhw.komponentenarchitekturen.knauf.security.SecuredRemote</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. Die Action j_security_check
ist im JSP-Standard festgelegt und definiert ein festes Ziel für die Login-Verarbeitung.
<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.
<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>
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.
Im folgenden wird EJB-Injection verwendet.
Die Klasse de.fhw.komponentenarchitekturen.knauf.security.SecurityClient
wird zugefügt und in "Manifest.mf" als "Main-Class" eingetragen.
In der Main-Methode geschehen die Aufrufe der SecuredBean
. Diese wird per Injection gesetzt:
public class SecurityClient
{
@EJB()
public static SecuredRemote secured;
Im Client muss der Login am Server initialisiert werden. Hierfür gibt es zwei Wege (https://docs.wildfly.org/19/WildFly_Elytron_Security.html#Client_Authentication_with_Elytron_Client):
Variante 1: "wildfly-config.xml":
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<authentication-client xmlns="urn:elytron:1.0">
<authentication-rules>
<rule use-configuration="ejbConfig">
<match-host name="127.0.0.1"/>
</rule>
</authentication-rules>
<authentication-configurations>
<configuration name="ejbConfig">
<set-user-name name="admin"/>
<credentials>
<clear-password password="admin"/>
</credentials>
</configuration>
</authentication-configurations>
</authentication-client>
<jboss-ejb-client xmlns="urn:jboss:wildfly-client-ejb:3.0">
<connections>
<connection uri="remote+http://127.0.0.1:8080" />
</connections>
</jboss-ejb-client>
</configuration>
Diese Datei stammt aus dem WildFly-Quickstart EJB-Security per JAAS. Relevant ist hier das markierte Fragment,
in dem Username und Passwort fix auf "admin"/"admin" gesetzt werden. Wie man sieht, besteht hier keine Möglichkeit, die Anmeldedaten dynamisch zu setzen. Dieser Weg wäre also nur gangbar für Anwendungen,
die immer mit festen Logindaten arbeiten.
Ich habe keine Doku zu den Elementen dieser Datei gefunden.
Deshalb Variante 2: Programmatisch
Für den folgenden Code müssen ab WildFly 24 drei JAR-Dateien zum ClassPath zugefügt werden, die im Server-Plugin leider fehlen (https://issues.redhat.com/browse/JBIDE-28597).
Es handelt sich um folgende Dateien (Versionsnummern stammen aus WildFly 26.1):
- "%WILDFLY_HOME%\modules\system\layers\base\org\wildfly\security\elytron-base\main\wildfly-elytron-client-1.19.0.Final.jar" (enthält z.B. "org.wildfly.security.auth.client.AuthenticationContext" etc)
- "%WILDFLY_HOME%\modules\system\layers\base\org\wildfly\security\elytron-base\main\wildfly-elytron-sasl-1.19.0.Final" (enthält z.B. "org.wildfly.security.sasl.SaslMechanismSelector")
- "%WILDFLY_HOME%\modules\system\layers\base\org\wildfly\security\elytron-base\main\wildfly-elytron-credential-1.19.0.Final.jar (wird indirekt benötigt:
The type org.wildfly.security.password.Password cannot be resolved. It is indirectly referenced from required .class files)
Das Ergebnis sieht so aus:
Der Code sieht so aus:
import javax.ejb.EJBAccessException;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.auth.client.MatchRule;
import org.wildfly.security.sasl.SaslMechanismSelector;
public static void main(String[] args)
{
// Benutzername/Passwort abfragen:
String login = SecurityClient.inputString("Login eingeben: ");
String passwort = SecurityClient.inputString("Password eingeben: ");
// create your authentication configuration
AuthenticationConfiguration adminConfig = AuthenticationConfiguration.empty()
// .useProviders( () -> new Provider[] { new WildFlyElytronProvider() })
// .setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("DIGEST-MD5"))
.setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("PLAIN")) // Gemäß Forum nicht nötig!!!
.useRealm("knaufsecurity") // Scheint auch ohne zu klappen!!!
.useName(login).usePassword(passwort);
// create your authentication context
AuthenticationContext context = AuthenticationContext.empty();
// context = context.with(MatchRule.ALL.matchHost("127.0.0.1"), adminConfig);
context = context.with(MatchRule.ALL, adminConfig);
// create your runnable for establishing a connection
Runnable runnable = new Runnable()
{
public void run()
{
try
{
// Aufrufen der Methoden:
System.out.println("Teste forAdminOnly.... ");
try
{
secured.forAdminOnly();
System.out.println("Success !");
}
catch (EJBAccessException ex)
{
// Zugriffsfehler: das ist der erwartete Zustand, wenn man den in diesem Fall ungeeigneten Login eingibt.
System.out.println("Failed (" + ex.getClass().getName() + "): " + ex.getMessage() + " ");
}
catch (Exception ex)
{
// Sonstiger Fehler (falsch konfigurierte Sicherheit?): alles ausgeben.
ex.printStackTrace();
}
System.out.println("Teste forKundeOnly.... ");
try
{
secured.forKundeOnly();
System.out.println("Success !");
}
catch (EJBAccessException ex)
{
// Zugriffsfehler: das ist der erwartete Zustand, wenn man den in diesem Fall ungeeigneten Login eingibt.
System.out.println("Failed (" + ex.getClass().getName() + "): " + ex.getMessage() + " ");
}
catch (Exception ex)
{
// Sonstiger Fehler (falsch konfigurierte Sicherheit?): alles ausgeben.
ex.printStackTrace();
}
System.out.println("Teste forBoth.... ");
try
{
secured.forBoth();
System.out.println("Success !");
}
catch (EJBAccessException ex)
{
// Zugriffsfehler: das ist der erwartete Zustand, wenn man den in diesem Fall ungeeigneten Login eingibt.
System.out.println("Failed (" + ex.getClass().getName() + "): " + ex.getMessage() + " ");
}
catch (Exception ex)
{
// Sonstiger Fehler (falsch konfigurierte Sicherheit?): alles ausgeben.
ex.printStackTrace();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
// use your authentication context to run your client
context.run(runnable);
}
Hier wird zuerst eine org.wildfly.security.auth.client.AuthenticationConfiguration
erzeugt. Wichtig sind hier die Methoden useName
und usePassword
.
Der Aufruf von setSaslMechanismSelector(SaslMechanismSelector.NONE.addMechanism("PLAIN"))
sorgt für die Verwendung unverschlüsselten Klartext-Logindaten und ist der Default, könnte also
weggelassen werden. Der Aufruf von useRealm
scheint irrelevant zu sein.
Als nächstes wird ein org.wildfly.security.auth.client.AuthenticationContext
erzeugt, an den die eben erzeugte AuthenticationConfiguration
übergeben wird,
und außerdem wird ihr eine org.wildfly.security.auth.client.MatchRule
, die angibt, für welchen Zielserver die Konfiguration gilt.
Der eigentliche EJB-Zugriff wird in ein java.lang.Runnable
verpackt, das an context.run(runnable);
übergeben wird - dadurch wird der EJB-Zugriff nach erfolgreichem Login ausgeführt.
Zur Laufzeit sollen die JAR-Dateien für oben genutzte Security-Klassen ("%WILDFLY_HOME%\modules\system\layers\base\org\wildfly\security\elytron-private\main\wildfly-elytron-1.1.1.Final.jar" und
"%WILDFLY_HOME%\modules\system\layers\base\org\wildfly\common\main\wildfly-common-1.2.0.Final.jar", auch zu finden in "%WILDFLY_HOME%\bin\client\jboss-client.jar") vom Server zur Verfügung gestellt werden.
Damit dies klappt, muss man allerdings einen Eintrag für eine Modul-Abhängigkeit "org.wildfly.security.elytron" in "Manifest.mf" zufügen:
Manifest-Version: 1.0
Class-Path: SecurityEJB.jar
Main-Class: de.fhw.komponentenarchitekturen.knauf.security.SecurityClient
Dependencies: org.wildfly.security.elytron
Fehlerdiagnose: falls man clientseitige Security-Probleme hat, kann man in "%WILDFLY_HOME%\appclient\configuration\appclient.xml" die gleichen Logging-Einträge
vornehmen, die ich oben schon für "standalone.xml" angegeben hatte.
Das Einlesen von Usereingaben per "System.in"-Stream funktioniert mit WildFly 11.0 nicht, da dort
System.out, System.error und System.in
ersetzt wurden.
Die Ausgabestreams leiten auf einen Konsolen-Logger um.
System.in
wurde durch einen
NullInputStream
ersetzt.
Unter
https://issues.jboss.org/browse/WFLY-3737 findet sich mein Bugreport dazu - mal
schauen, was herauskommt.
Workaround:
System.console()
verwenden:
private String inputString(String title) throws IOException
{
System.out.print (title);
BufferedReader bufferedReader = new BufferedReader (System.console().reader());
String strInput = bufferedReader.readLine();
return strInput;
}
Security-Domain für die ganze Anwendung
Alternativ zu der Deklaration der Security-Domain in Web- und EJB-Projekt können 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"?>
<jboss-app xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-app_7_0.xsd"
version="7.0">
<security-domain>security-beispiel-domain</security-domain>
</jboss-app>
Ohne Annotations
ejb-jar.xml
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd"
version="3.2">
<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>
<business-remote>de.fhw.komponentenarchitekturen.knauf.security.SecuredRemote</business-remote>
<ejb-class>de.fhw.komponentenarchitekturen.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.komponentenarchitekturen.knauf.security.SecuredBean
</injection-target-class>
<injection-target-name>entityManager</injection-target-name>
</injection-target>
</persistence-context-ref>
<!--SessionContext-Injection -->
<resource-env-ref>
<resource-env-ref-name>EgalWasHierSteht</resource-env-ref-name>
<resource-env-ref-type>javax.ejb.SessionContext</resource-env-ref-type>
<mapped-name>java:comp/EJBContext</mapped-name>
<injection-target>
<injection-target-class>de.fhw.komponentenarchitekturen.knauf.security.SecuredBean</injection-target-class>
<injection-target-name>sessionContext</injection-target-name>
</injection-target>
</resource-env-ref>
</session>
</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:
- 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.
- Injection des SessionContexts: hier gibt es drei Besonderheiten:
a) das XML-Pflichtelement <resource-env-ref-name>
kann hier einen beliebigen Wert haben, denn da wir uns den SessionContext nicht per JNDI-Lookup selbst holen wollen, ist es egal
unter welchem Namen er im JNDI zur Verfügung gestellt wird.
b) Der SessionContext steht automatisch im Environment Naming Context der Bean unter dem Namen java:comp/EJBContext
zur Verfügung (auch wenn er nicht im JNDI-View auftaucht), auf diesen Namen verweist unsere <resource-env-ref>
mittels des Elements <mapped-name>
.
c) Die Angabe des Elements <resource-env-ref-type>
mit dem Datentyps des Elements (javax.ejb.SessionContext)
ist optional, aber kann nicht schaden ;-).
"orm.xml" sieht so aus:
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
version="2.1">
<entity class="de.fhw.komponentenarchitekturen.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="passwort">
<column name="PASSWORT" />
</basic>
<many-to-many name="rollen" target-entity="de.fhw.komponentenarchitekturen.knauf.security.RolleBean">
<join-table name="BENUTZER_ROLLE">
<join-column name="BENUTZER_ID"/>
<inverse-join-column name="ROLLEN_ID"/>
</join-table>
</many-to-many>
</attributes>
</entity>
<entity class="de.fhw.komponentenarchitekturen.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>
Einzige Besonderheit ist die Deklaration des Join-Tables.
In einer Datei "META-INF\jboss-ejb3.xml" deklarieren wir die Security Domain:
<?xml version="1.1" encoding="UTF-8"?>
<jboss:ejb-jar xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-2_0.xsd http://java.sun.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-ejb3-spec-2_0.xsd"
version="3.1"
xmlns:s="urn:security:1.1"
impl-version="2.0">
<assembly-descriptor>
<s:security>
<!-- Even wildcard * is supported -->
<!-- <ejb-name>MyBean</ejb-name>-->
<ejb-name>*</ejb-name>
<s:security-domain>knaufsecurity</s:security-domain>
<s:run-as-principal></s:run-as-principal>
<s:missing-method-permissions-deny-access>false</s:missing-method-permissions-deny-access>
</s:security>
</assembly-descriptor>
</jboss:ejb-jar>
Man beachte die relativ verwirrende Deklaration der Namespaces für das Subelement "security".
Das Element "ejb-name" muss angegeben werden. Ich habe hier den Stern als Wildcard für alle EJBs verwendet, ich hätte aber auch "SecuredBean" eintragen können.
Die Elemente "run-as-principal" und "missing-method-permissions-deny-access" habe ich nur angegeben, weil die XSD sie als Pflichtfelder erfordert - es geht auch ohne.
Bei ersterem habe ich nichts zur Bedeutung gefunden, das zweite Element gibt das Verhalten des Servers an, wenn auf der gesicherten EJB eine Methode
keine "@RolesAllowed"-Annotation (bzw. "method-permission" in ejb-jar.xml) definiert hat: "true" verweigert den Zugriff, "false" erlaubt ihn (Default).
Hinweis 1: Für den Namespace "http://java.sun.com/xml/ns/javaee" ist als SchemaLocation die Datei "jboss-ejb3-spec-2_0.xsd" definiert. Grund: diese über-definiert einige Elemente von "ejb-jar_3_1.xsd",
siehe https://issues.jboss.org/browse/WFLY-3189. Die Deklaration wäre nicht nötig gewesen, der JBossTools-Plugin bindet sich an den Namespace "http://java.sun.com/xml/ns/javaee",
der wiederum mit "jboss-ejb3-spec-2_0.xsd" verlinkt ist.
Hinweis 2: Der Namespace "urn:security:1.1" zeigt auf die Datei "jboss-ejb-security_1_1.xsd". Diese gibt es nicht auf der JBoss-Webseite, deshalb kann ich hier keine Schema Location definieren.
Auch hier rettet uns der JBossTools-Plugin.
Siehe https://docs.wildfly.org/19/Developer_Guide.html#Securing_EJBs und
https://docs.wildfly.org/19/Developer_Guide.html#jboss-ejb3
Anmerkung:
In früheren Versionen (JBoss 6) erfolgte diese Deklaration in einer Datei "jboss.xml". Diese wird von WildFly 8 und neuer nicht mehr unterstützt.
Im Client nutze ich ebenfalls Injection. Deshalb benötigen wir hier die Datei "application-client.xml"
<?xml version="1.0" encoding="UTF-8"?>
<application-client xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/application-client_7.xsd"
version="7">
<display-name>SecurityClient</display-name>
<ejb-ref>
<ejb-ref-name>ejb/Secured</ejb-ref-name>
<ejb-ref-type>Session</ejb-ref-type>
<remote>de.fhw.komponentenarchitekturen.knauf.security.SecuredRemote</remote>
<injection-target>
<injection-target-class>de.fhw.komponentenarchitekturen.knauf.security.SecurityClient</injection-target-class>
<injection-target-name>secured</injection-target-name>
</injection-target>
</ejb-ref>
</application-client>
Außerdem muss diese EJB-Referenz an einen JNDI-Namen gebunden werden, und das geht über "jboss-client.xml":
<?xml version="1.0" encoding="UTF-8"?>
<jboss-client xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee
http://www.jboss.org/j2ee/schema/jboss-client_6_0.xsd"
version="6.0">
<jndi-name>SecurityClient</jndi-name>
<!-- Connect the declared EJB reference to the JNDI-Name of the EJB: -->
<ejb-ref>
<ejb-ref-name>ejb/Secured</ejb-ref-name>
<jndi-name>java:global/Security/SecurityEJB/SecuredBean!de.fhw.komponentenarchitekturen.knauf.security.SecuredRemote</jndi-name>
</ejb-ref>
</jboss-client>
Beide Dateien enthalten keine Spezialitäten im Vergleich zu früheren Beispielen.
Die modifizierte Version des Projekts gibt es hier: SecurityNoAnnotation.ear.
ACHTUNG: Dieses Projekt kann nicht neben dem obigen Security-Beispiel auf den gleichen Server deployed werden
Stand 07.08.2022
Historie:
01.10.2017: Erstellt aus 2014er-Beispiel, angepasst an WildFly 11
07.11.2017: Workaround "WildFly-Jars im ApplicationClient-Projekt hinzufügen" ist nicht mehr nötig.
16.04.2020: einige Links auf WildFly-Dokus aktualisiert, Link auf den Bug "WFLY-13379"
07.08.2022: WildFly 26, "jboss-web.xml" auf XSD 12.1 aktualisiert und Link auf Bug "JBMETA-418".
In "index.jsp" unnötiges "PortableRemoteObject.narrow" entfernt (Compilefehler in Java 11).
Elytron-Jars müssen händisch zugefügt werden