get-the-solution

Glassfish 4 and Realm

By
on Regards: Java;

Issues when setting a Glassfish 4 with JDBCRealm with a JSF Project

OR WHY GLASSFISH 4 AND JDBCREALM DROVE ME NUTS

Die Benutzerauthentifizierung kann in einer Java-EE-Webapplikationen theoretisch ohne großen Aufwand realisiert werden, da die Java Authentication and Authorization Service(JAAS) von Haus aus mitgeliefert wird. Grundsätzlich gibt es mehrere Realm Typen, wobei ich mich aber auf den JDBCRealm beschränke der es erlaubt die Benutzer samt Rollen in einer Datenbank zu hinterlegen.

Man beginnt damit eine Datenbankverbindung zu definieren. Dazu erstellt man im Netbeans einen (New > Other > GlassFish >) JDBC Connection Pool und als nächstes eine (New > Other > GlassFish >) JDBC Resource. Diese Informationen sollten in der neu erstellten Datei glassfish-resources.xml hinterlegt sein. Beim deployen werden diese Informationen vom Glassfish Server übernommen.

Denn theoretisch kann man die JDBC Resource alias JNDI auch in der Glassfish Administrations-Konsole erstellen. Es wäre aber ganz schön umständlich, wenn man diese Informationen jedesmal händisch bei einem Serverwechsel, oder bei einer Neuinstallation erneut eintragen müsste. (siehe Abschnitt Domain Configuration)

[...]   
<jdbc-resource jndi-name="jdbc/wiekocheich"
                   enabled="true" object-type="user" pool-name="mysql_wiekocheich_getPool"/>

Aus meiner Entity Class wurden folgende Tabellen generiert (das verschlüsselte Passwort für den Benutzer martin lautet hier password1):

CREATE TABLE IF NOT EXISTS `c_user` (
  `EMAIL` varchar(50) NOT NULL,
  `FIRSTNAME` varchar(20) NOT NULL,
  `LASTNAME` varchar(20) NOT NULL,
  `PASSWORD` varchar(64) NOT NULL,
  `REGISTEREDON` datetime NOT NULL,
  PRIMARY KEY  (`EMAIL`),
  UNIQUE KEY `EMAIL` (`EMAIL`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `c_user` (`EMAIL`, `FIRSTNAME`, `LASTNAME`, `PASSWORD`, `REGISTEREDON`) VALUES
('user.nobody@provider.net', 'martin', 'friedrich', '0b14d501a594442a01c6859541bcb3e8164d183d32937b8518', '2014-02-07 07:23:22');

CREATE TABLE IF NOT EXISTS `c_user_c_user_role` (
  `User_EMAIL` varchar(50) NOT NULL,
  `groups_ROLENAME` varchar(20) NOT NULL,
  PRIMARY KEY  (`User_EMAIL`,`groups_ROLENAME`),
  KEY `FK_c_user_c_user_role_groups_ROLENAME` (`groups_ROLENAME`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `c_user_c_user_role` (`User_EMAIL`, `groups_ROLENAME`) VALUES
('user.nobody@provider.net', 'headchef');

CREATE TABLE IF NOT EXISTS `c_user_role` (
  `ROLENAME` varchar(20) NOT NULL,
  PRIMARY KEY  (`ROLENAME`),
  UNIQUE KEY `UNQ_c_user_role_0` (`ROLENAME`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

INSERT INTO `c_user_role` (`ROLENAME`) VALUES
('cook'),
('headchef');

Wobei die letzte Tabelle c_user_role, die sämtliche verfügbaren Rollen speichert, vom JDBCRealm nicht berücksichtigt wird. Als Passwortverschlüsselung kommt SHA-256 zum Einsatz. Da SHA-256 einen 256 Bit Hash generiert und pro Zeichen 4 Bit benötigt werden, kann man den varhcar auf 64 setzen.

Die Passwörter können mithilfe der Google Guava Bibliothek generiert werden. (import com.google.common.hash.Hashing;)

String hash=Hashing.sha256().hashString(password, Charsets.UTF_8).toString();

(Quelle: jsfsecure)

Beim Login Formular hat man nun die Möglichkeit zwischen zwei Varianten zu entscheiden. Bei der ersten wird kein Code-Behind benötigt, der xhtml Code ist völlig ausreichend für die Anmeldung des Benutzers. (Siehe Secure your application loginForm.xhtml.) Wichtig zu erwähnen ist, dass keinesfalls die vordefinierten Attribute action=“j_security_check” geändert oder gar gelöscht werden dürfen. Diese sorgen dafür, dass das Formular an einen hidden Servlet geschickt und dort verarbeitet wird. Die andere Möglichkeit wäre sich selber um die Anmeldung zu kümmern siehe JSF Login-Seite login.xhtml und LoginBean.java.

Nach dem die Login- und Logout- Seite erstellt worden sind, kann man die web.xml mit dem Netbeans Editor bearbeiten und an die Security Optionen herangehen. Diese sind mehr oder weniger selbst erklärend. Man setzt die “Login Konfiguration” auf “Form” und gibt die Login- und Errorseiten an. Netbeans fügt hier anscheinend nicht das servlet-mapping Pattern an. Dieses muss man von selbst nachbearbeiten! Also z.B. aus login.xhtml wird /faces/login.xhtml. Ich habe mein Servlet Mapping anders deklariert, deshalb ist der login und error Eintrag ohne “/faces/*.xhtml” angegeben, lasst euch also nicht verwirren.

Der Realm Name (bei mir loginRealm) dient als Referenz welche man später in der Glassfish 4 Administrations-Konsole angeben muss. Bei den Security Roles gibt man die gespeicherten Rollen die man in der DB angelegt hat an. Das wären bei der Verwendung der oben angeführten Datensätze cook und headchef.

Zu guter Letzt erstellt man noch einen Constraint in dem wir unseren geschützen Bereich festlegen. Der Name kann willkürlich gesetzt werden und im URL Pattern können wir unseren Adminbereich schützen. Laut dem “How to Create a Secure JSF/JPA Web App on Glassfish 4” Video sollte man nur die HTTP Methoden GET und POST aktiviert haben, da die anderen Probleme verursachen können.

Mit den Role Name(s) legt man fest, welche Benutzerrollen auf diesen Bereich zugreifen dürfen. Der User Data Constrain ist für Benutzer die ein SSL Zertifikat besitzen interessant.

 <security-constraint>
        <display-name>administration-constraint</display-name>
        <web-resource-collection>
            <web-resource-name>administration</web-resource-name>
            <description>authorisation area</description>
            <url-pattern>/edit/*</url-pattern>
            <http-method>GET</http-method>
            <http-method>POST</http-method>
        </web-resource-collection>
        <auth-constraint>
            <description/>
            <role-name>headchef</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>loginRealm</realm-name>
        <form-login-config>
            <form-login-page>/viewLogin.jsf</form-login-page>
            <form-error-page>/viewError.jsf</form-error-page>
        </form-login-config>
    </login-config>
    <security-role>
        <description>Administrator</description>
        <role-name>headchef</role-name>
    </security-role>
    <security-role>
        <description>normal user which can create recipes</description>
        <role-name>cook</role-name>
    </security-role>

Nun muss man dem Glassfish noch das Benutzer- und Rollenmapping erklären. Über New > Other > GlassFish > Glassfish Descriper wird die Datei glassfish-web.xml angelegt. Ein Mapping an sich brauchen wir nicht und deshalb geben wir unter > Security bei “cook” >add group > “cook” erneut an. Das Gleiche machen wir analog für headchef.

  <security-role-mapping>
    <role-name>cook</role-name>
    <group-name>cook</group-name>
  </security-role-mapping>
  <security-role-mapping>
    <role-name>headchef</role-name>
    <group-name>headchef</group-name>
  </security-role-mapping>

Jetzt geht es an das Eingemachte. Wir öffnen die Glassfish 4 Administrations-Konsole ( http://localhost:4848 ) und wählen Configurations > server-config > Security > realms. Wir erstellen einen neuen Realm mit dem Namen den wir bei der web.xml angegeben haben (loginRealm) und verwenden als Class Name “com.sun.enterprise.security.ee.auth.realm.jdbc.JDBCRealm”.

Beim JAAS Context muss man “jdbcRealm” setzen, alles andere ist ungültig! Warum die Entwickler hier ein normales inputfeld angegeben haben, ist schon merkwürdig. (siehe auch https://www.java.net/node/672073)

Beim JNDI gibt man die zu Beginn erstellte JDBC Resource an, was “jdbc/wiekocheich” entspricht. Als nächstes folgen die Angaben der Tabellen- und Spaltennamen. Im Unterschied zu Glassfish 3 ist es bei der Version 4 nicht möglich die Passwörter quasi blank abzuspeichern. Auch das Setzen bei der Verschlüsselungstechnik auf none hilft nicht weiter. Wir verwenden bei Password Encryption Algorithm SHA-256. Damit das Passwort auch richtig codiert wird, muss man noch das Charset angeben, welches bei der Passwortgeneration verwendet wird (UTF-8).

Nun sollte das Einloggen funktionieren.

edit realm

Die Einstellungen werden dann in der Datei glassfish/domains/domain1/config/domain.xml abgespeichert.

Hier ein Auszug aus dem erstellten Realm:

<auth-realm name="loginRealm" classname="com.sun.enterprise.security.ee.auth.realm.jdbc.JDBCRealm">
          <property name="jaas-context" value="jdbcRealm"></property>
          <property name="password-column" value="PASSWORD"></property>
          <property name="datasource-jndi" value="java/jboss/datasources/MysqlDS"></property>
          <property name="group-table" value="wiekocheich.c_user_c_user_role"></property>
          <property name="user-table" value="wiekocheich.c_user"></property>
          <property name="group-name-column" value="groups_ROLENAME"></property>
          <property name="digestrealm-password-enc-algorithm" value="SHA-256"></property>
          <property name="user-name-column" value="EMAIL"></property>
          <property name="charset" value="UTF-8"></property>
          <property name="group-table-user-name-column" value="User_EMAIL"></property>
</auth-realm>

Jede Änderung an einem Realm erfordert einen neustart des Glassfish Servers! Abschließend sei angemerkt, dass die Exceptions und die Fehlerlogs vom Glassfish Server nicht sehr hilfreich waren auch wenn man das Loglevel auf Finest gesetzt hat.

Hier noch weitere Tutorials zum Thema:

Weitere Referenzen zum Thema: