get the solution


Page 1 of 3123

Glassfish 4 and Realm

By
on Regards: Archive; Other;

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.

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:


Page 1 of 3123

reinstall grub

By
on Regards: Archive; Infrastructure;

Problem: Windows overwrite grub. Grub missing.

Solution:

  • Use a live linux to get access to your hdd. Use for example unetbootin with ubuntu live
  • Open a terminal
  • Use fdisk -l to find out where your linux system is installed
  • For example our linux system is located on sda1
  • mkdir /mnt/debian
  • mount /dev/sda1 /mnt/debian
  • ls /mnt/debian/home/yourusername # check if the mounted partition is the correct system
  • For using chroot we need some preparation
  • cp -L /etc/resolv.conf /mnt/debian/tc/
  • mount -t proc none /mnt/debian/proc
  • mount –rbind /sys /mnt/debian/sys
  • mount –rbind /dev /mnt/debian/dev
  • chroot /mnt/debian /bin/bash
  • grub-install /dev/sda1
  • update-grub
  • exit
  • shutdown now -r


Page 1 of 3123

MySQL Cursor alternative – Dynamische SQL Cursor

By
on Regards: Archive; DBMS;

vor kurzem stand ich vor dem Problem, dass ich in einer MySQL Stored Procedure einen Cursor benötigt habe. Das Problem an der ganzen Sache war, dass für die Cursor Deklatraion einige Abfragen (u.a insert into, select usw.) vorausgesetzt wurden. Das Cursor-Statment wird quais während der Laufzeit “generiert”.

Das hat zur Folge, dass man einen Deklaration Block hat auf den eben die nötigen Abfragen zum Zusammenstellen des Cursor-Statments machen muss. Erst dann kann der Cursor mit DECLARE definiert werden. Das Problem wie folgt:

CREATE PROCEDURE `tsc_sp_add_page` (in idfolder int(11), in title varchar(65), in template varchar(100))
COMMENT 'creates a new page and adds all wildcards which will be shown infolder,InLanguage,InAll,InPage'
BEGIN
--id of the inserted page
DECLARE pageid INT(11);
...ein paar Deklaration
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
...dann die vorausgesetzten Abfragen für die Definition des Cursors
--get the language of the page
set pagelangid = (SELECT f.idlanguage as pagelangid FROM tsc_folder AS f WHERE f.id=idfolder LIMIT 0 , 1);
--first we create the page record
INSERT INTO `tsc_page` (`id` ,`idfolder` , `title` , `template`) VALUES (NULL , idfolder, title , template);
set pageid = LAST_INSERT_ID();
...cursor deklaration ist abhänig vom insert into statment
declare c_wildcardInAll cursor FOR select distinct w.id from tsc_wildcard AS w, tsc_page_have_wildcard AS wh, 
tsc_page AS p, tsc_folder AS f
WHERE wh.idpage = p.id
AND wh.idwildcard = w.id
AND p.idfolder = f.id
AND w.InLanguage =1
AND f.idlanguage =pagelangid;

Laut MySQL Syntax müssen aber alle Deklaration ganz zu Beginn der SP stehen. Nach der ersten Abfrage darf keine weitere Deklaration mehr folgen. Mit dem obigen Code-Ausschnitt erhält man dann die Meldung:

Error code 1064, SQL state 42000: You have an error in your SQL syntax; 
check the manual that corresponds to your MySQL server version for the right syntax to use near
'declare c_wildcardInAll cursor FOR select distinct w.id from tsc_wildcard AS w, ' at line 29
Line 7, column 1

Jetzt wäre es toll, wenn es so etwas wie dynamische Cursor oder Parameter Cursor geben würde, was aber in MySQL 5.0.24 nicht der Fall war (Evtl wird ein solches Feature bei der nächsten Version nachgereicht?, Orcael kennt z.B. Cursor mit Parameter).

Um keine Cursor verwenden zu müssen, kann man eine temporäre Tabelle verwenden, die quasi als Cursor fungiert. Die temporäre Tabelle muss genau auf die Abfrage angepasst werden. Zusätzlich benötigt man eine weitere RowID Spalte um in der Schleife auf alle Datensätze zugreifen zu können, bzw. um iterieren zu können.

1.) Temporäre Tabelle erstellen

Da ich bei meiner Abfrage nur eine Spalte abfrage, sieht meine temporäre Tabelle recht simpel aus.

CREATE TEMPORARY TABLE twildcardInLang (
`RowID` int(11) NOT NULL auto_increment PRIMARY KEY,
`id` int NOT NULL
) ENGINE=MEMORY;

2.) Daten abfragen und in die Tabelle schreiben

Wichtig ist, dass die RowID die Eigenschaften auto_increment und PRIMARY KEY hat. Ansonsten kann man nicht durch die Datensätze iterieren. Danach fügen wir die Datensätze in die temporäre Tabelle ein.

INSERT INTO twildcardInLang (id) select distinct w.id from tsc_wildcard AS w, tsc_page_have_wildcard AS wh, tsc_page AS p, tsc_folder AS f
WHERE wh.idpage = p.id
AND wh.idwildcard = w.id
AND p.idfolder = f.id
AND w.InLanguage =1
AND f.idlanguage =pagelangid;

3.)Hilfsvariable für die Schleife setzen

Nun müssen wir wissen wie oft wir die Schleife durchlaufen wollen. Dies entspricht logischerweise der Anzahl der Datensätze in unserer temporären Tabelle. Als nächstes setzen wir die Zähl-Variable auf 1 um die Schleife durchlaufen- und die Daten der Tabelle abfragen zu können.

SET @NumberRecords = (select count(id) as id from twildcardInLang);
SET @RowCount = 1;

4.) Die Schleife erstellen

Der Code der Schleife sollte nun für sich sprechen.

WHILE @RowCount <= @NumberRecords DO
set @wildcardid = (SELECT id FROM twildcardInLang WHERE RowID = @RowCount);
...irgendwas mit @wildcardid machen
SET @RowCount := (@RowCount + 1);
END WHILE;

5.) Am Schluss entfernen wir die temporäre Tabelle mit DROP TABLE twildcardInLang;

Der komplette Code-Ausschnitt sieht dann in etwa so aus:

CREATE TEMPORARY TABLE twildcardInLang (
`RowID` int(11) NOT NULL auto_increment PRIMARY KEY,
`id` int NOT NULL
) ENGINE=MEMORY;

--get InLanguage=pagelangid wildcards with proper language wildcards and insert it into our temporary table (which we use as "virtual curosr")
INSERT INTO twildcardInLang (id) select distinct w.id from tsc_wildcard AS w, tsc_page_have_wildcard AS wh, tsc_page AS p, tsc_folder AS f
WHERE wh.idpage = p.id
AND wh.idwildcard = w.id
AND p.idfolder = f.id
AND w.InLanguage =1
AND f.idlanguage =pagelangid;
-- Get the number of records in the temporary table
SET @NumberRecords = (select count(id) as id from twildcardInLang);
SET @RowCount = 1;
WHILE @RowCount <= @NumberRecords DO
set @wildcardid = (SELECT id FROM twildcardInLang WHERE RowID = @RowCount);
-- link wildcard with page
INSERT INTO `tsc_page_have_wildcard` (`idpage`, `idwildcard`) VALUES (pageid, @wildcardid);
SET @RowCount := (@RowCount + 1);
END WHILE;
DROP TABLE twildcardInLang;
...

 

Links zum Thema:

http://www.sqlbook.com/SQL/Avoiding-using-SQL-Cursors-20.aspx

http://dev.mysql.com/doc/refman/5.0/en/while-statement.html


Page 1 of 3123

Subversion – Repositories zusammenlegen – exportieren

By
on Regards: Archive; Infrastructure;

Vor kurzem stand ich vor dem Problem mehrere Repositories die ich mit Subversion verwalte zu einer einzigen zusammen zu führen. Das geht recht einfach.

  1. Neue Repositorie anlegen (z.B. mit “svnadmin create REPOS_PATH” oder über websvn)
  2. Ins Verzeichnis wechseln wo die Repositories gespeichert sind
  3. Alle Repositories die man jetzt in die neue Repositorie laden bzw. einfügen will müssen mit dump exportiert werden.
  4. Könnte z.B. so aussehen: fes-a120d19nas:~# svnadmin dump /c/.subversion/Repositoriename1/ > /backupRepositoriename1.dump
  5. Danach muss man nur noch alle dumps in die neue Repositorie laden
  6. Könnte z.B. so aussehen: fes-a120d19nas:~# svnadmin load /c/.subversion/UnsereNeueRepositoriename/ < /backup/Repositoriename1.dump
  7. Danach könnt ihr die nicht benötigten Repositories z.B. so ” rm /c/.subversion/AlteRepositorie/ -rf” löschen

Allerdings kann es sein, dass in der alten und in der neuen Repositorie die Ordnernamen gleich heißen. Das führt zu einem Konflikt und der “load” Vorgang kann nicht fortgesetzt werden.

Das wird höchstwahrscheinlich mit den Ordnern branches, trunk, tags passieren.

Man muss also dafür sorgen, dass entweder der “dump” oder die Zielrespositorie diesen Ordner nicht enthält.

Man kann sie mit dem Befehl “svn rm http://server/svn/respositorie/trunk” löschen. Notfalls wäre ein Umbennen des Ordners ebenfalls möglich mit  “svn mv http://server/svn/respositorie/trunk http://server/svn/respositorie/trunk1”
Auch immer gut zu wissen: svn aus einem Verzeichnis löschen:

find . -name “.svn” -exec rm -rf {} \;

Links:

http://svnbook.red-bean.com/en/1.1/re29.html
http://de.wikipedia.org/wiki/Apache_Subversion


Page 1 of 3123