Now that you have the server running, we will use the Duke’s Bank example from the J2EE tutorial to illustrate how to get an application up and running in JBoss. Duke’s Bank demonstrates a selection of J2EE technologies working together to implement a simple on-line banking application. It uses EJBs and web components (JSPs and servlets) and uses a database to store the information. The persistence is bean-managed, with the entity beans containing the SQL statements which are used to manipulate the data.
We won’t look in detail at its functionality or comment on the implementation but will concentrate on a step-by-step guide to building and deploying it in JBoss.
You should have already downloaded the J2EE 1.4 tutorial, which includes Duke’s Bank. We’ll go through building and deploying the application first and then look at things in a bit more detail.
You should be able to obtain the supplementary JBoss files from the same place as this document. The file is packaged as a ZIP archive called jbossj2ee-src.zip. Download this and unpack it into the j2eetutorial14 directory, adding to the existing tutorial files. All the Duke’s Bank code is in a the examples/bank subdirectory. If you've unpacked the JBoss extensions correctly, you will see a jboss-build.xml there. This is our Ant build8 script for the JBoss version of the application. Rather than just overwriting the existing build.xml file, we’ve used a different name from the default. This means that Ant must now be run as ant -f jboss-build.xml.
Before we can build, you'll need to edit the jboss-build.properties file in the j2eetutorial14 to point to your JBoss install directory. Set the jboss.home property to the full path to your JBoss 4.0 installation. If you’ve unpacked JBoss 4.0 in the C: drive on a windows machine, you would set it as follows.
# Set the path to the JBoss directory containing the JBoss application server # (This is the one containing directories like "bin", "client" etc.) jboss.home=C:/jboss-4.0.1
At the command line, go to the bank directory. All the build commands will be run from here. Compilation is pretty straightforward; just type the following command to invoke the compile Ant target.
ant -f jboss-build.xml compile
If there aren’t any errors, you will find a newly created build directory with the class files in it.
The application has one EJB jar, bank-ejb.jar, which contains the code and descriptors (ejb-jar.xml and jboss.xml) for the entity beans and associated controller session beans which the clients interact with. The package-ejb Ant target will create them in the jar directory.
ant -f jboss-build.xml package-ejb
The next target is the web application which provides the front end to allow users to interact with the business components (the EJBs). The web source (JSPs, images etc.) is contained in the src/web directory and is added unmodified to the archive. The Ant war task also adds a WEB-INF directory which contains the files which aren’t meant to be directly accessed by a web browser but are still part of the web application. These include the deployment descriptors (web.xml and jboss-web.xml), class files, (e.g. servlets and EJB interfaces) and extra jars and the extra JSP tag-library descriptors required by the web application. The package-web Ant target builds the web client WAR file.
ant -f jboss-build.xml package-web
In addition to the web interface, there is a standalone Java client for administering customers and accounts. You can build it using the package-client Ant target.
ant -f jboss-build.xml package-client
The generated WAR file contains the application-client.xml and jboss-client.xml descriptors as well as the client jndi.properties file. The client JAR will also be included as an additional module in the EAR file and the server.
The EAR file is the complete application, containing the three EJB modules and the web module. It must also contain an additional descriptor, application.xml. It is also possible to deploy EJBs and web application modules individually but the EAR provides a convenient single unit. The assemble-app Ant target will produce the final file JBossDukesBank.ear.
ant -f jboss-build.xml assemble-app
Before we can deploy the application, we need to populate the database it will run against. If you are writing an application that uses container-managed persistence, you can configure the CMP engine to create the tables for you at deployment, but otherwise you will need have to have a set of scripts to do the job. This is also a convenient place pre-populating the database with data.
The HSQL database can be run in one of two modes: in-process or client-server (the HSQL documentation refers to this as server mode). Since we are going to be running the SQL scripts using a tool that connects to the database, we want to make sure the database is running in client-server mode and will accept TCP/IP connections. In later versions of JBoss, client-server mode is disabled to prevent direct database access, which could be a security risk if the default login and password have not been modified. Open the hsqldb-ds.xml file which you’ll find in the deploy directory and which sets up the default datasource. Near the top of the file, you’ll find the connection-url element. Make sure the value is set to jdbc:hsqldb:hsql://localhost:1701 and that any other connection-url elements are commented out.
<!-- The jndi name of the DataSource, it is prefixed with java:/ -->
<!-- Datasources are not available outside the virtual machine -->
<jndi-name>DefaultDS</jndi-name>
<!-- for tcp connection, allowing other processes to use the hsqldb
database. This requires the org.jboss.jdbc.HypersonicDatabase mbean. -->
<connection-url>jdbc:hsqldb:hsql://localhost:1701</connection-url>
<!-- for totally in-memory db, not saved when jboss stops.
The org.jboss.jdbc.HypersonicDatabase mbean is unnecessary
<connection-url>jdbc:hsqldb:.</connection-url>
-->
<!-- for in-process db with file store, saved when jboss stops. The
org.jboss.jdbc.HypersonicDatabase is unnecessary
<connection-url>jdbc:hsqldb:${jboss.server.data.dir}/hypersonic/localDB
</connection-url>
-->
Now scroll down to the bottom of the file, and you should find the MBean declaration for the Hypersonic service.
<mbean code="org.jboss.jdbc.HypersonicDatabase" name="jboss:service=Hypersonic"> <attribute name="Port">1701</attribute> <attribute name="Silent">true</attribute> <attribute name="Database">default</attribute> <attribute name="Trace">false</attribute> <attribute name="No_system_exit">true</attribute> </mbean>
Make sure this is also uncommented so JBoss will start the database in the correct mode. If you choose to delete the other MBean definition, make sure to change the dependency on the datasource from jboss:service=Hypersonic,database=localDB to jb oss:service=Hypersonic.
We have supplied scripts to run with HSQL in the sql directory. The database tasks in the build file will try to contact the HSQL database. If JBoss isn’t already running, you should start it now, so that the database is available.
First we need to create the necessary tables with the db-create-table target.
ant -f jboss-build.xml db-create-table
Then run the db-insert target to populate them with the required data.
ant -f jboss-build.xml db-insert
Finally, if everything has gone according to plan, you should be able to view some of the data using the db-list target, which lists the transactions for a specific account.
ant -f jboss-build.xml db-list
Just as a quick aside at this point, start up the JMX console application web application and click on the service=Hypersonic link which you’ll find under the section jboss. If you can’t find this, make sure the service is enabled as described in Section 4.1.7.1, “Enabling the HSQL MBean and TCP/IP Connections”.
This will take you to the information for the Hypersonic service MBean. Scroll down to the bottom of the page and click the invoke button for the startDatabaseManager() operation. This starts up the HSQL Manager, a Java GUI application which you can use to manipulate the database directly.
Deploying an application in JBoss is easy. You just have to copy the EAR file to the deploy directory. The deploy target in the build file does this for our application.
ant -f jboss-build.xml deploy
You should see something close to the following output from the server:
18:07:53,923 INFO [EARDeployer] Init J2EE application: f ile:/private/tmp/jboss-4.0.2/server/default/deploy/JBossDukesBank.ear 18:07:55,024 INFO [EjbModule] Deploying CustomerBean 18:07:55,103 INFO [EjbModule] Deploying AccountBean 18:07:55,142 INFO [EjbModule] Deploying TxBean 18:07:55,403 INFO [EjbModule] Deploying NextIdBean 18:07:55,439 INFO [EjbModule] Deploying AccountControllerBean 18:07:55,478 INFO [EjbModule] Deploying CustomerControllerBean 18:07:55,503 INFO [EjbModule] Deploying TxControllerBean 18:07:56,950 INFO [EJBDeployer] Deployed: file:/private/tmp/jboss-4.0.2/server/default/t mp/deploy/tmp15097JBossDukesBank.ear-contents/bank-ejb.jar 18:07:57,267 INFO [TomcatDeployer] deploy, ctxPath=/bank, warUrl=file:/private/tmp/jboss -4.0.2/server/default/tmp/deploy/tmp15097JBossDukesBank.ear-contents/web-client.war/ 18:08:00,784 INFO [EARDeployer] Started J2EE application: file:/private/tmp/jboss-4.0.2/ server/default/deploy/JBossDukesBank.ear
If there are any errors or exceptions, make a note of the error message and at what point it occurs (e.g. during the deployment of a particular EJB, the web application or whatever). Check that the EAR is complete and inspect the WAR file and each of the EJB jar files to make sure they contain all the necessary components (classes, descriptors etc.).
You can safely redeploy the application if it is already deployed. To undeploy it you just have to remove the archive from the deploy directory. There’s no need to restart the server in either case. If everything seems to have gone OK, then point your browser at the application URL.
http://localhost:8080/bank/main
You will be forwarded to the application login page. As explained in the tutorial, you can login with a customer ID of 200 and the password j2ee. Figure 4.2, “Duke's Bank” shows the Duke's Bank application in action.
If you got an error at this point, check again that you have set up the database correctly as described in Section 4.1.7.1, “Enabling the HSQL MBean and TCP/IP Connections”. In particular, check that the connection-url is correct and that you have populated the database with data.
You can also run the standalone client application using the run-client target.
ant -f jboss-build.xml run-client
This is a Swing GUI client which allows you to administer the customers and accounts.
It’s worth taking a brief look at the use of JNDI with standalone clients. The example makes use of the J2EE Application Client framework, which introduces the concept of a client-side local environment naming context within which JNDI names are resolved with the prefix java:/comp/env. This is identical to the usage on the server side; the additional level of indirection means you can avoid using hard-coded names in the client. The name mapping is effected by the use of the proprietary jboss-client.xml which resolves the references defined in the standard application-client.xml.
One issue with a Java client is how it bootstraps itself into the system, how it manages to connect to the correct JNDI server to lookup the references it needs. The information is supplied by using standard Java properties. You can find details of these and how they work in the JDK API documentation for the javax.naming.Context class. The properties can either be hard-coded, or supplied in a file named jndi.properties on the classpath. The file we’ve used is shown below.
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory java.naming.provider.url=jnp://localhost:1099 java.naming.factory.url.pkgs=org.jboss.naming.client j2ee.clientName=bank-client
The first three are standard properties, which are set up in order to use the JBoss JNDI implementation. The j2ee.clientName property identifies the client deployment information on the server side. The name must match the jndi-name specified in the jboss-client.xml descriptor:
<jboss-client>
<jndi-name>bank-client</jndi-name>
<ejb-ref>
<ejb-ref-name>ejb/customerController</ejb-ref-name>
<jndi-name>MyCustomerController</jndi-name>
</ejb-ref>
<ejb-ref>
<ejb-ref-name>ejb/accountController</ejb-ref-name>
<jndi-name>MyAccountController</jndi-name>
</ejb-ref>
</jboss-client>
Of course if you were only building a simple web application, you wouldn't need to worry about remote clients
While we’re on the subject of JNDI, let’s take a quick look at the JBoss JMX console again and see what information it shows about our application. This time click on the service=JNDIView link and then invoke the list() operation, which displays the JNDI tree for the server. You should see the EJB modules from Duke’s Bank listed near the top and the contents of their private environment naming contexts as well as the names the entries are linked to in the server. Here's an abbreviated view:
Ejb Module: bank-ejb.jar java:comp namespace of the CustomerBean bean: +- env (class: org.jnp.interfaces.NamingContext) java:comp namespace of the AccountBean bean: +- env (class: org.jnp.interfaces.NamingContext) java:comp namespace of the TxBean bean: +- env (class: org.jnp.interfaces.NamingContext) java:comp namespace of the NextIdBean bean: +- env (class: org.jnp.interfaces.NamingContext) java:comp namespace of the AccountControllerBean bean: +- env (class: org.jnp.interfaces.NamingContext) | +- ejb (class: org.jnp.interfaces.NamingContext) | | +- tx[link -> MyTx] (class: javax.naming.LinkRef) | | +- nextId[link -> MyNextId] (class: javax.naming.LinkRef) | | +- account[link -> MyAccount] (class: javax.naming.LinkRef) | | +- customer[link -> MyCustomer] (class: javax.naming.LinkRef) java:comp namespace of the CustomerControllerBean bean: +- env (class: org.jnp.interfaces.NamingContext) | +- ejb (class: org.jnp.interfaces.NamingContext) | | +- tx[link -> MyTx] (class: javax.naming.LinkRef) | | +- nextId[link -> MyNextId] (class: javax.naming.LinkRef) | | +- account[link -> MyAccount] (class: javax.naming.LinkRef) | | +- customer[link -> MyCustomer] (class: javax.naming.LinkRef) java:comp namespace of the TxControllerBean bean: +- env (class: org.jnp.interfaces.NamingContext) | +- ejb (class: org.jnp.interfaces.NamingContext) | | +- tx[link -> MyTx] (class: javax.naming.LinkRef) | | +- nextId[link -> MyNextId] (class: javax.naming.LinkRef) | | +- account[link -> MyAccount] (class: javax.naming.LinkRef) | | +- customer[link -> MyCustomer] (class: javax.naming.LinkRef) java: Namespace +- XAConnectionFactory (class: org.jboss.mq.SpyXAConnectionFactory) +- DefaultDS (class: org.jboss.resource.adapter.jdbc.WrapperDataSource) +- SecurityProxyFactory (class: org.jboss.security.SubjectSecurityProxyFactory) +- DefaultJMSProvider (class: org.jboss.jms.jndi.JNDIProviderAdapter) +- comp (class: javax.naming.Context) +- JmsXA (class: org.jboss.resource.adapter.jms.JmsConnectionFactoryImpl) +- ConnectionFactory (class: org.jboss.mq.SpyConnectionFactory) +- jaas (class: javax.naming.Context) | +- HsqlDbRealm (class: org.jboss.security.plugins.SecurityDomainContext) | +- jmx-console (class: org.jboss.security.plugins.SecurityDomainContext) | +- jbossmq (class: org.jboss.security.plugins.SecurityDomainContext) | +- JmsXARealm (class: org.jboss.security.plugins.SecurityDomainContext) +- timedCacheFactory (class: javax.naming.Context) Failed to lookup: timedCacheFactory, errmsg=org.jboss.util.TimedCachePolicy +- TransactionPropagationContextExporter (class: org.jboss.tm.TransactionPropagationContextFactory) +- StdJMSPool (class: org.jboss.jms.asf.StdServerSessionPoolFactory) +- Mail (class: javax.mail.Session) +- TransactionPropagationContextImporter (class: org.jboss.tm.TransactionPropagationContextImporter) +- TransactionManager (class: org.jboss.tm.TxManager)
Note that these objects are created on demand, so the dukesbank entry will only appear if you have configured the application to use the dukesbank domain and have tried to log in to the application.
When you first access Duke's Bank, you are prompted for an account number and password using a simple login form. J2EE security always requires some configuration in the application server. The authentication logic which decides whether a login succeeds or fails is outside the scope of the J2EE specification. The actual authentication mechanism is controlled by the security domain that the application is linked to. In this section we will see how the security domain is configured for Duke's Bank.
The standard J2EE security declarations for the web and EJB tiers are declared in the web.xml and ejb-jar.xml files, respectively. However, the JBoss security configuration needs to go in the companion JBoss deployment descriptors.
The configuration is quite straightforward. In both cases, a single security-domain element is all that is needed. The following fragment from jboss-web.xml illustrates the usage in the web application.
<jboss-web> <security-domain>java:/jaas/dukesbank</security-domain> ... </jboss-web>
The configuration looks the same on the EJB side. The following jboss.xml file shows how this works.
<jboss> <security-domain>java:/jaas/dukesbank</security-domain> <enterprise-beans> ... </enterprise-beans> </jboss>
In both cases, we've linked to a security domain located by the java:/jaas/dukesbank JNDI name. All security domains are bound under the java:/jaas context, so we would really just say that the application is using the dukesbank security domain.
Security domains are configured by a corresponding application policy in the conf/login-config.xml file. But, if you look, you won't see a dukesbank policy. When JBoss doesn't find a matching policy, it defaults to using the other policy, which is shown below.
<application-policy name="other"> <authentication> <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required" /> </authentication> </application-policy>
The login module used here uses local properties files for authentication. There are two files; one provides usernames and passwords, and other provides the roles assigned to the users. The following listing shows the users.properties file used for Duke's Bank.
# users.properties file for use with the UsersRolesLoginModule # Format is: # # username=password 200=j2ee
The format is simple. Each line takes the form username=password. So, this file contains one user named 200 whose password is j2ee. That is the name and password you used to access the application. Try changing the password and deploying the application again.
J2EE application security isn't driven only by a username and password. Users are assigned to roles, and the application can only only give access to a user based on that user's roles. Duke's bank can only be accessed by users that are customers, as indicated by the bankCustomer role. The following listing shows the roles.properties file used to assign the bankCustomer role to the user.
# A roles.properties file for use with the UsersRolesLoginModule # # Format is # # username=role1,role2,role3 200=bankCustomer
To complete the application, you should actually define the dukesbank security domain rather than letting the server fall back to the default security domain. All you need to do is add the following policy to the conf/login-config.xml file.
<application-policy name="dukesbank"> <authentication> <login-module code="org.jboss.security.auth.spi.UsersRolesLoginModule" flag="required" /> </authentication> </application-policy>
You will, unfortunately, need to restart JBoss to make the changes to login-config.xml visible.
As you can well imagine, password files are not a very flexible method for maintaining security. In a real project you will want to use a more sophisticated approach to authentication. Since the user accounts are in the database, it would be very convenient to be able to store the passwords there too. We'll do that now.
JBoss comes with a login module called DatabaseServerLoginModule that can use authentication information stored in a relational database. The following database schema mirrors the information in the properties files.
CREATE TABLE Users(username VARCHAR(64) PRIMARY KEY, passwd VARCHAR(64)) CREATE TABLE UserRoles(username VARCHAR(64), userRoles VARCHAR(32)) INSERT INTO Users VALUES ('200','j2ee') INSERT INTO UserRoles VALUES ('200','bankCustomer')
You can use they Hypersonic database manager we looked at earlier to create these tables and load the data. Once you have the data, we'll need to configure the DatabaseServerLoginModule. The login module requires the appropriate queries to retrieve the password and roles for a particular user and a reference to the datasource that those queries should be issued on. For Duke's Bank, the following configuration should be added to login-config.xml.
<application-policy name="dukesbank"> <authentication> <login-module code="org.jboss.security.auth.spi.DatabaseServerLoginModule" flag="required"> <module-option name="dsJndiName">java:/DefaultDS</module-option> <module-option name="principalsQuery"> select passwd from Users where username=? </module-option> <module-option name="rolesQuery"> select userRoles,'Roles' from UserRoles where username=? </module-option> </login-module> </authentication> </application-policy>
The query to retrieve the password is straightforward. However, there is an additional field with value Roles in the roles query. This is the role group. It allows you to store additional roles (for whatever purpose) classified by the role group. The ones which will affect JBoss permissions are expected to have the value Roles. In this simple example we only have a single set of roles in the database and no role group information. The details aren't critical to understand here. Just remember that the roles query should return the text Roles in the second column.
You will need to restart JBoss for changes to login-config.xml to take effect. Do that and access Duke's bank.
The login modules we’ve used so far all have support for password hashing; rather than storing passwords in plain text, a one-way hash of the password is stored (using an algorithm such as MD5) in a similar fashion to the /etc/passwd file on a UNIX system. This has the advantage that anyone reading the hash won’t be able to use it to log in. However, there is no way of recovering the password should the user forget it, and it also makes administration slightly more complicated because you also have to calculate the password hash yourself to put it in your security database. This isn’t a major problem though. To enable password hashing in the database example above, you would add the following module options to the configuration
<module-option name="hashAlgorithm">MD5</module-option> <module-option name="hashEncoding">base64</module-option>
This indicates that we want to use MD5 hashes and use base64 encoding to covert the binary hash value to a string. JBoss will now calculate the hash of the supplied password using these options before authenticating the user, so it’s important that we store the correctly hashed information in the database. If you’re on a UNIX system or have Cygwin installed on Windows, you can use openssl to hash the value.
$ echo -n "j2ee" | openssl dgst -md5 -binary | openssl base64 glcikLhvxq1BwPBZN0EGMQ==
You would then insert the resulting string, glcikLhvxq1BwPBZN0EGMQ==, into the database instead of the plaintext password, j2ee. If you don’t have this option, you can use the class org.jboss.security.Base64Encoder which you’ll find in the jbosssx.jar file.
$ java -classpath ./jbosssx.jar org.jboss.security.Base64Encoder j2ee MD5 [glcikLhvxq1BwPBZN0EGMQ==]
With a single argument it will just encode the given string but if you supply the name of a digest algorithm as a second argument it will calculate the hash of the string first.