piatok, 25. septembra 2009

How to deal with password hash in JBoss Seam

Recently I tried to clarify some points concerning authentication part of Seam security. Here can be found forum thread, where I gave some advice on this subject and which was inspiration for this blog entry: How to persist user?

So what it is all about? Seam provides a standard API for the management of a Seam application's users and roles, called Identity Management. In order to use Seam's Identity Management it must be configured with Identity Store. Out of the box Seam provides Jpa Identity Store which stores users and roles in database.

Apparently developer asking for help discovered identityManager component, which can be used to create new users, however in order to use this component user must be already authenticated and additionally must posses some permissions. (Permissions and authorization in general are not covered in this article.)

But this presents CATCH XXII situation when there is an empty database and there is no possibility to log in. In short, developer can not use identityManager.createUser(String name, String password) method because he can not log in and he can not log in because there are no user accounts in database to log in with.

Well no problem, developers can populate their brand new database with SQL scripts manually or in case of Seam, import-dev.sql file can be employed to do the job. But still there is one catch, Seam can automatically hash passwords in database and it is not trivial to reproduce this manually.

This article explains first shortly how to set up Jpa Identity Store using hashed password with salt and then how gather all information needed to create SQL script, which inserts first user into database.

Firstly Jpa Identity Store must be configured in components.xml descriptor like so:
<security:identity-manager identity-store="#{jpaIdentityStore}"/>
<security:jpa-identity-store 
user-class="com.acme.model.User"
role-class="com.acme.model.Role"/>
Where User class is Entity annotated with seam annotations like so:
(only getters are shown, fields and setters are omitted for clarity)
@Entity
public class User implements Serializable {
...
 @NotNull
 @UserPrincipal
 @Column(unique = true)
 public String getUsername() { return username; }

 @UserPassword
 public String getPasswordHash() { return passwordHash; }

 @PasswordSalt
 public String getSalt() { return salt; }

 @UserEnabled
 public boolean isEnabled() { return enabled; }

 @UserFirstName
 public String getFirstname() { return firstname; }

 @UserLastName
 public String getLastname() { return lastname; }

 @UserRoles
 @ManyToMany
 public List<UserRole> getRoles() { return roles; }
...
}
Most noticeable annotations are @UserPrincipal, @UserPassword and @PasswordSalt.
In field annotated @UserPrincipal username is stored.
In field annotated @PasswordSalt salt is stored.
In field annotated @UserPassword hashed password is stored.
Password hash depends on these parameters:

passwordHash = f(password, algorithm, salt, iterations)

Where:

password - plain password
algorithm - hashing algorithm, in Seam 2.2.0.GA, HmacSHA1 is default
salt - salt value used to produce the password hash, when not specified field annotated with @UserPrincipal is used (username is used as salt for hashing password)
iterations - number of iterations for generating the password hash, default value is 1000

Solution:

I have created small test which does the trick, it uses Seam PasswordHash component and it produces valid password, salt and hash combination which can be used in SQL script. This code was tested only with Seam 2.2.0.GA. (This version uses default hashing algorithm, algorithm parameter can be introduced, but it depends on security provider implementation so it is out of scope for this article.)
public class PasswordHashTest extends SeamTest {

    @Test
    public void testPasswordHash() throws Exception {
        new ComponentTest() {
            @Override
            protected void testComponents() throws Exception {
                Log log = Logging.getLog(PasswordHashTest.class);
                PasswordHash passwordHash = PasswordHash.instance();

                final byte[] salt = passwordHash.generateRandomSalt();
                final int iterations = 1000;
                final String password = "admin";
                final String hash = passwordHash.createPasswordKey(password.toCharArray(), salt, iterations);

                assert hash != null : "Hash not calculated!";

                log.info("Password: " + password);
                log.info("Salt: " + BinTools.bin2hex(salt));
                log.info("Iterations: " + iterations);
                log.info("Hash: " + hash);
            }
        }.run();
    }
}
After running this test, all information needed is logged as info message. This test can be run multiple times in case more user accounts are desired.

streda, 2. septembra 2009

Jednoduché generovanie manifestu pomocou nástroja ANT


Pohľad na bojové pole
Na jednej strane je J2EE aplikácia zabalená ako EAR, vygenerovaná seam-gen skriptom, ktorá obsahuje EJB modul JAR a webovú časť aplikácie WAR.
Zjednodušená štruktúra aplikácie:
  • myapp.ear
    • myapp-ejb.jar
    • lib
      • richfaces-api.jar
      • ...
    • myapp-web.war
      • WEB-INF
        • lib
          • richfaces-ui.jar
          • richfaces-impl.jar
          • ...
  • myapp.ear/lib - je adresár, ktorý obsahuje knižnice implicitne viditeľné iba z myapp-ejb.jar
  • myapp.ear/myapp-web.war/WEB-INF/lib - knižnice v tomto adresári sú naopak viditeľné len pre myapp-web.war
Na druhej strane, je celkom bežná potreba, použiť niektoré knižnice na oboch miestach. Ako riešiť túto situáciu? Skopírovanie danej knižnice do oboch lib adresárov problém nerieši. Pretože sa môže napríklad stať, že trieda z danej knižnice je návratovou hodnotu metódy session beanu použitej v servlete. V tomto prípade by si web kontajner sťažoval, že trieda, ktorú mu vrátil session bean do servletu nieje tá, ktorú skutočne očakával.

Riešenie
Jedno z riešení spočíva v tom, že knižnice z adresáru myapp.ear/myapp-web.war/WEB-INF/lib okopírujeme do myapp.ear/lib a vytvoríme manifest súbor s classpath a umiestnime ho sem myapp.ear/myapp-web.war/META-INF/MANIFEST.MF.
Modifikovaná štruktúra aplikácie:
  • myapp.ear
    • myapp-ejb.jar
    • lib
      • richfaces-api.jar
      • richfaces-ui.jar
      • richfaces-impl.jar
      • ...
    • myapp-web.war
      • META-INF
        • MANIFEST.MF
Pričom obsah súboru MANIFEST.MF by vyzeral takto:

Class-Path: lib/richfaces-impl.jar lib/richfaces-ui.jar

Za povšimnutie stojí, že cesta v manifeste, je relatívna ku koreňu EAR súboru. Výsledkom je, že všetky knižnice sú viditeľné pre EJB modul a pre web modul sú viditeľné knižnice richfaces-impl.jar a richfaces-ui.jar. Tento manifest súbor sa dá samozrejme napísať aj ručne, ale zvyčajne je v classpath-e oveľa viac záznamov a keďže povolená dĺžka riadku je v manifest špecifikácii je obmedzená ľahko sa môže stať, že ručne napísaný manifest bude chybný.

Generovanie MANIFEST.MF s classpath pomocou ANT
Projekt vygenerovaný seam-gen skriptom používa na kopírovanie knižníc do war súboru následujúcu časť kódu.
<copy todir="${war.dir}/WEB-INF/lib">
  <fileset dir="${lib.dir}">
      <includesfile name="deployed-jars-war.list"/>
      <exclude name="jboss-seam-gen.jar"/>
      <exclude name="jboss-seam-debug.jar" unless="is.debug"/>
  </fileset>
</copy>
  • deployed-jars-war.list je textový súbor, ktorý obsahuje zoznam knižníc, ktoré sa kopírujú do myapp.ear/myapp-web.war/WEB-INF/lib (na jednom riadku je jedno meno knižnice)
V tomto prípade by deployed-jars-war.list vyzeral takto:

richfaces-impl.jar
richfaces-ui.jar


Modifikovaný ANT skript, ktorý nič nekopíruje, ale namiesto toho vytvorí manifest súbor s classpath záznamom.
<path id="war.classpath.path">
   <fileset dir="${lib.dir}">
       <includesfile name="deployed-jars-war.list"/>
       <exclude name="jboss-seam-gen.jar"/>
       <exclude name="jboss-seam-debug.jar" unless="is.debug"/>
   </fileset>
</path>

<manifestclasspath property="war.classpath" jarfile="${project.name}_war">
   <classpath refid="war.classpath.path" />
</manifestclasspath>

<mkdir dir="${war.dir}/META-INF"/>

<manifest file="${war.dir}/META-INF/MANIFEST.MF">
   <attribute name="Class-path" value="${war.classpath}"/>
</manifest>

nedeľa, 2. augusta 2009

JBoss clustering konfigurácia pre jeden počítač

Tento tutoriál by mal pomôcť pri nastavení jednoduchého JBoss klastra na jednej pracovnej stanici s operačným systémom Windows Vista. Konfigurácia má význam iba pre programátorov, ktorí potrebujú otestovať, alebo vyvíjať svoj program na klastri, ale nemajú k dispozícii viac počítačov.

Konfigurácia Microsoft Loopback adaptéru s dvoma IP adresami
  1. Ovládací panel/Pridanie hardvéru
  2. Inštalovať hardvér vybratý manuálne v zozname
  3. Vybrať - Sieťové adaptéry
  4. Výber sieťového adaptéra/Microsoft/Microsoft Loopback Adapter
  5. Premenovanie názvu siete na "Loopback" kôli prehľadnosti - Ovládací panel/Sieťové pripojenia
  6. Sieťové pripojenie Loopback/Vlastnosti
  7. Internet Protocol Version 4 (TCP/IPv4)
  8. Nastavenie prvej IP adresy 192.168.1.140 (maska podsiete 255.255.255.0), na ktorej bude počúvať node1
  9. Spresniť/Pridať - Nastavenie druhej IP adresy 192.168.1.141 (maska podsiete 255.255.255.0), na ktorej bude počúvať node2
  10. Otvorenie Windows konzoly/príkaz ipconfig /all, ak je adaptér dobre nainštalovaný a sú nakonfigurované obe IP adresy výstup bude vypadať obdobne ako na obrázku

Inštalácia aplikačného serveru JBoss
  1. Aplikačný server je možné stiahnuť na tejto adrese: http://www.jboss.org/jbossas/downloads/
  2. Rozpakovať/Vytvoriť dva nové profily, node1 a node2, profily vytvoríme tak, že okopírujeme profil all do dvoch nových adresárov node1 a node2 (profily sa nachádzajú v adresári %JBOSS_HOME%/server), adresárová štruktúra by mala vypadať ako na obrázku
  3. V konzole príkaz %JBOSS_HOME%/bin/run.bat -c node1 -b 192.168.1.140 -Djboss.messaging.ServerPeerID=1 spustí profil node1, ktorý bude počúvať na adrese 192.168.1.140 a -Djboss.messaging.ServerPeerID=1 nastaví unikátne ID pre messaging servis, v konzole by mal byť výpis, ktorý hovorí, že klaster má jedného člena, obdobne ako na obrázku
  4. V druhom konzolovom okne spustíme príkaz %JBOSS_HOME%/bin/run.bat -c node2 -b 192.168.1.141 -Djboss.messaging.ServerPeerID=2 a v logu by sme mali vidieť hlásenie o tom, že klaster má už dvoch členov


Ak sa všetko podarilo, tak node1 v prvej konzole by mal zareagovať obdobným logom ako je tento:
18:00:38,092 INFO  [DefaultPartition] New cluster view for partition DefaultPartition (id: 1, delta: 1) : [192.168.1.140:1099, 192.168.1.141:1099]
18:00:38,092 INFO  [RPCManagerImpl] Received new cluster view: [192.168.1.140:49924|1] [192.168.1.140:49924, 192.168.1.141:51781]
18:00:38,108 INFO  [DefaultPartition] I am (192.168.1.140:1099) received membershipChanged event:
18:00:38,108 INFO  [DefaultPartition] Dead members: 0 ([])
18:00:38,108 INFO  [DefaultPartition] New Members : 1 ([192.168.1.141:1099])
18:00:38,108 INFO  [DefaultPartition] All Members : 2 ([192.168.1.140:1099, 192.168.1.141:1099])

nedeľa, 26. júla 2009

How to configure PostgreSQL datasource in Embedded JBoss

I have spent more than hour fighting this issue. I will wrote it down rather than forgetting about it. So that in case I have to do this again I will know where to look, and I hope that it might be helpful for others as well.

Environment: Seam 2.1.2, PostgreSQL, JBoss embedded

Scenario: Project generated by seam-gen originally used Hypersonic SQL database (HSQLDB). Later Postgre SQL database (PGSQL) was installed and properly configured with dev build profile. However tests which are running in embedded JBoss still use HSQLDB, the goal is to configure tests to run on PGSQL as well.

Documentation says: By default, a generated project will use the java:/DefaultDS (a built in HSQL datasource in Embedded JBoss) for testing. If you want to use another datasource place the foo-ds.xml into bootstrap/deploy directory.

Sounds easy considering that dev profile is already configured with PGSQL datasource which is woking with ordinary JBoss 5.1.0. One would assume that it is sufficient to copy myproject-dev-ds.xml file to bootstrap/deploy directory (rename it to myproject-test-ds.xml for clarity sake), then change persistence-test.xml according to persistence-dev.xml and all should work.

But in case this is done, running ant test task produces only this incomprehensible error:
org.jboss.deployers.client.spi.IncompleteDeploymentException: Summary of incomplete deployments (SEE PREVIOUS ERRORS FOR DETAILS):
*** CONTEXTS MISSING DEPENDENCIES: Name -> Dependency{Required State:Actual State}
jboss.jca:name=myProjectDatasource,service=DataSourceBinding
-> jboss:service=invoker,type=jrmp{Start:** NOT FOUND **}
-> jboss:service=invoker,type=jrmp{Create:** NOT FOUND **}
*** CONTEXTS IN ERROR: Name -> Error
jboss:service=invoker,type=jrmp -> ** NOT FOUND **
at org.jboss.deployers.plugins.deployers.DeployersImpl.checkComplete(DeployersImpl.java:576)
at org.jboss.deployers.plugins.main.MainDeployerImpl.checkComplete(MainDeployerImpl.java:559)
at org.jboss.embedded.Bootstrap.bootstrapURL(Bootstrap.java:149)
at org.jboss.embedded.Bootstrap.bootstrap(Bootstrap.java:183)
at org.jboss.embedded.Bootstrap.bootstrap(Bootstrap.java:195)
at org.jboss.seam.mock.EmbeddedBootstrap.startAndDeployResources(EmbeddedBootstrap.java:11)
at org.jboss.seam.mock.AbstractSeamTest.startJbossEmbeddedIfNecessary(AbstractSeamTest.java:1024)
at org.jboss.seam.mock.AbstractSeamTest.startSeam(AbstractSeamTest.java:915)
at org.jboss.seam.mock.SeamTest.startSeam(SeamTest.java:58)
... Removed 15 stack frames
where myproject-test-ds.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
<local-tx-datasource>
  <jndi-name>myProjectDatasource</jndi-name>

  <use-java-context>false</use-java-context><!-- note this line -->

  <connection-url>jdbc:postgresql://localhost:5432/test</connection-url>
  <driver-class>org.postgresql.Driver</driver-class>
  <user-name>test</user-name>
  <password>test</password>
</local-tx-datasource>
</datasources>
and persistence-test.xml looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Persistence deployment descriptor for test profile -->
<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="myProject">
  <provider>org.hibernate.ejb.HibernatePersistence</provider>

  <jta-data-source>myProjectDatasource</jta-data-source><!-- note this line -->

  <properties>
      <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
      <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
      <property name="hibernate.show_sql" value="true"/>
      <property name="hibernate.format_sql" value="true"/>
      <property name="jboss.entity.manager.factory.jndi.name" value="java:/myProjectEntityManagerFactory"/>
  </properties>
</persistence-unit>
</persistence>
So both files seem to be configured properly, how come it is not working then? Solution can be found here: https://jira.jboss.org/jira/browse/JBPAPP-2223.

Workaround Description: remove "false" from the datasource file and add "java:/" prefix to the content of jta-data-source element in persistence.xml

And indeed after changing myproject-test-ds.xml like this:
<?xml version="1.0" encoding="UTF-8"?>
<datasources>
 <local-tx-datasource>
     <jndi-name>myProjectDatasource</jndi-name>
     <connection-url>jdbc:postgresql://localhost:5432/test</connection-url>
     <driver-class>org.postgresql.Driver</driver-class>
     <user-name>test</user-name>
     <password>test</password>
 </local-tx-datasource>
</datasources>
and persistence-test.xml like this:
<?xml version="1.0" encoding="UTF-8"?>
<!-- Persistence deployment descriptor for test profile -->
<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="myProject">
     <provider>org.hibernate.ejb.HibernatePersistence</provider>

     <jta-data-source>java:/myProjectDatasource</jta-data-source><!-- note this line -->

     <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="create-drop"/>
         <property name="hibernate.show_sql" value="true"/>
         <property name="hibernate.format_sql" value="true"/>
         <property name="jboss.entity.manager.factory.jndi.name" value="java:/myProjectEntityManagerFactory"/>
     </properties>
 </persistence-unit>
</persistence>
everything works like a charm.

piatok, 24. júla 2009

Ako na to HTTPS, JBoss a Seam

Toto je krátky návod, ktorý by mal pomôcť pri konfigurácii HTTPS komunikácie vo vývojom prostredí, ktoré obsahuje aplikačný server JBoss 5.1.0.GA a aplikáciu vygenerovanú nástrojom seam-gen vo verzii 2.1.2.
  1. Vygenerovanie "selfsigned" certifikátu ak nemáme žiadny k dispozícii.
  2. Nastavenie JBoss HTTPS konektora.
  3. Konfigurácia stránok v Seame.
Vygenerovanie "selfsigned" certifikátu ak nemáme žiadny k dispozícii.

Na generovanie použijeme nástroj keytool, ktorý sa štandardne nachádza v Sun JDK.
cd %JAVA_HOME%\bin

C:\Program Files\Java\jdk1.6.0_10\bin\keytool -genkeypair -alias devServerCert -keyalg RSA -validity 1500 -keystore devServer.keystore

Enter keystore password: devserver
Re-enter new password: devserver
What is your first and last name?
[Unknown]:  Meno Priezvisko
What is the name of your organizational unit?
[Unknown]:  oddelenie
What is the name of your organization?
[Unknown]:  spolocnost
What is the name of your City or Locality?
[Unknown]:  mesto
What is the name of your State or Province?
[Unknown]:  stat
What is the two-letter country code for this unit?
[Unknown]:  sk
Is CN=Meno Priezvisko, OU=oddelenie, O=spolocnost, L=mesto, ST=stat, C=sk correct?
[no]:  yes

Enter key password for devservercert enter
 (RETURN if same as keystore password):
Je potrebné si uvedomiť, že heslo pre kľúč je také isté ako heslo pre keystore, preto je ako vstup na konci iba enter. Vygenerovaný súbor devServer.keystore okopírujeme do ...jboss-5.1.0.GA\server\default\conf\ za predpokladu, že používame profil default.

Nastavenie JBoss HTTPS konektora.

Za predpokladu, že používame profil default je potrebné zmeniť súbor ...\jboss-5.1.0.GA\server\default\deploy\jbossweb.sar\server.xml takto:
...
   <Connector protocol="HTTP/1.1" SSLEnabled="true"
        port="8443" address="${jboss.bind.address}"
        scheme="https" secure="true" clientAuth="false"
        keystoreFile="${jboss.server.home.dir}/conf/devServer.keystore"
        keystorePass="devserver" sslProtocol = "TLS" />
...
kde:
keystoreFile - je cesta kde je uloženýdevServer.keystore súbor
keystorePass - je heslo pre keystore a zároveň aj pre klúč v ňom uložený

Konfigurácia stránok v Seame.
  • konfigurácia portov pre HTTP (8080) a HTTPS (8443) v štandartnom konfiguračnom súbore pages.xml
<?xml version="1.0" encoding="UTF-8"?>
<pages xmlns="http://jboss.com/products/seam/pages"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"

    no-conversation-view-id="/home.xhtml"
    login-view-id="/login.xhtml"
    http-port="8080"
    https-port="8443">
...
  • nastavenie pre konkrétnu stránku aby sa zobrazovala cez HTTPS protokol (v prípade, že request bude požadovať HTTP presmeruje sa na HTTPS), napríklad pre stránku login.xhtml stačí pridať scheme="https" do súboru login.page.xml
<?xml version="1.0" encoding="UTF-8"?>
<page xmlns="http://jboss.com/products/seam/pages"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.1.xsd"
   scheme="https">
...

utorok, 14. júla 2009

Nová firemná web stránka

Od dnes je k dispozícii nová firemná web stránka na www.zont.eu.

Announcing new company web page

Since today there is a new company web page available at www.zont.eu.