How to use Applets to access EJBs in JBoss

Author: Sacha Labourey <sacha.labourey@cogito-info.ch>

Note

This document applies to JBoss release 2.4 and later. If you are using an earlier version of JBoss upgrade to the 2.4 version or later.

Introduction

This chapter describes how to access EJBs running in JBoss from an Applet running in a browser.

When a JVM runs an Applet, it verifies its conformity to a set of security rules which have been established in order to protect the user from malicious code. Some of these rules are:

  • No access to the local filesystem

  • No access to any host on the Internet but the one from which the Applet was downloaded

  • Restricted access to system properties

Note

More information on the Applet security model can be found here: http://java.sun.com/sfaq/

These restrictions do have consequences on what can be done from a client Applet that want to access EJBs over the network.

Restricted (unsigned) Applet

The main consequence of the sandbox on EJB access from an Applet is that we will need to download the Applet from a Web server that has the same IP address as the J2EE server running the EJB we want to access. Nevertheless, it is still possible, thanks to IP traffic load-balancers for example, to have more than one server hidden behind a single IP address.

In our example, we will build a stateless session bean that simply returns the server date and hostname in a friendly message. To try this example, you’d better install the Java plug-in: support for Java in Web browsers in somewhat hazardous.

We first build our remote interface:

public interface TestApplet extends EJBObject
{
   public String getMessage() throws RemoteException;
}

And the Home interface:

public interface TestAppletHome extends EJBHome
{
   public TestApplet create() throws RemoteException, CreateException;
}

Now the simple EJB implementation:

public class TestAppletBean implements SessionBean
{
   private SessionContext sessionContext;

   public void ejbCreate() {}
   public void ejbRemove() {}
   public void ejbActivate() {}
   public void ejbPassivate(){}
   public void setSessionContext(SessionContext context)
   {
      sessionContext = context;
   }
   public String getMessage()
   {
      String hostname = "Unknown host";

      try
      {
         hostname = java.net.InetAddress.getLocalHost ().getHostName ();
      } catch (java.net.UnknownHostException uhe) { }

      String now = java.text.DateFormat.getDateInstance().format (new java.util.Date());

      System.out.println ("getMessage has been called from Applet!");

      return "Hello from " + hostname + ", here it is: " + now + ".";
   }
}

And its associated ejb-jar.xml descriptor:

<?xml version="1.0"?>

<!DOCTYPE ejb-jar PUBLIC '-//Sun Microsystems, Inc.//DTD Enterprise JavaBeans 1.1//EN' 'http://java.sun.com/j2ee/dtds/ejb-jar_1_1.dtd'>

<ejb-jar>
  <enterprise-beans>
    <session>
      <ejb-name>TestAppletBean</ejb-name>
      <home>org.jboss.docs.appletclient.interfaces.TestAppletHome</home>
      <remote>org.jboss.docs.appletclient.interfaces.TestApplet</remote>
      <ejb-class>org.jboss.docs.appletclient.ejb.TestAppletBean</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
    </session>
  </enterprise-beans>
  <assembly-descriptor>
    <container-transaction>
      <method>
	<ejb-name>TestAppletBean</ejb-name>
	<method-intf>Remote</method-intf>
	<method-name>*</method-name>
      </method>
      <trans-attribute>Required</trans-attribute>
    </container-transaction>
  </assembly-descriptor>
</ejb-jar>

We now implements a simple Applet that will have a single button. When pressed, the Applet will call the EJB and display its message.

public class AppletEjbCaller extends JApplet
{
   JPanel buttonPanel = new JPanel();
   JButton callEjbButton = new JButton();
   JPanel labelPanel = new JPanel();
   JLabel ejbMessageLabel = new JLabel();


   // Construct the Applet
   //
   public AppletEjbCaller () {}

   // Initialize the applet
   //
   public void init()
   {
      try
      {
         this.setSize(new Dimension(400, 65));
         callEjbButton.setText("Get server message");
         callEjbButton.addActionListener(new java.awt.event.ActionListener()
         {
            public void actionPerformed(ActionEvent e)
            {
               callEjbButton_actionPerformed(e);
            }
         });
         ejbMessageLabel.setText("no call performed yet.");
         this.getContentPane().add(buttonPanel, BorderLayout.CENTER);
         buttonPanel.add(callEjbButton, null);
         this.getContentPane().add(labelPanel, BorderLayout.SOUTH);
         labelPanel.add(ejbMessageLabel, null);
      }
      catch(Exception e)
      {
         e.printStackTrace();
      }
   }

   /**Get Applet information*/
   public String getAppletInfo()
   {
      return "JBoss EJB client Applet demo";
   }

   void callEjbButton_actionPerformed(ActionEvent e)
   {
      try
      {
         Properties jndiProps = new Properties() ;
         String myServer = this.getCodeBase().getHost ();
         jndiProps.setProperty("java.naming.factory.initial", "org.jnp.interfaces.NamingContextFactory" ) ;
         jndiProps.setProperty("java.naming.provider.url", myServer ) ;
         jndiProps.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces" ) ;
         TestAppletHome home = (TestAppletHome)PortableRemoteObject.narrow( new InitialContext(jndiProps).lookup( "TestAppletBean" ),
                                                                            TestAppletHome.class) ;
         TestApplet remote = home.create() ;
         ejbMessageLabel.setText( remote.getMessage() ) ;
      }
      catch ( SecurityException se )
      {
         se.printStackTrace ();
         ejbMessageLabel.setText (se.toString ());
      }
      catch( Exception ex )
      {
         System.err.println( "APPLET" );
         ex.printStackTrace();
      }
   }
}

One of the most important part of this Applet are these two lines:

String myServer = this.getCodeBase().getHost ();
jndiProps.setProperty("java.naming.provider.url", myServer ) ;

We make sure that we access the server from which we have just been downloaded. Attempt to access another server would raise a security exception.

Warning

you need to access your HTML page from the hostname or IP address to which the EJB server is bound! For the sandbox, 192.168.1.1 and 127.0.0.1 are two different hosts even if in reality they represent the same host.

Consequently, if you access your web page through http://127.0.0.1/TestApplet.html and, in your code, attempt to reach your EJB at IP 192.168.1.1, an exception will be raised.

Un-trusted Applets are very sensitive to their environment! For example, imagine you access your web page through the 127.0.0.1 IP address. The Applet will use this address when performing the lookup on the JNDI tree to get the home proxy. This will work. But you have no control on:

  • The codebase address used by the RMI subsystem on the server to allow dynamic code downloading

  • The RMI target that the proxy holds

In consequence, as soon as the first EJB invocation will be fired or as soon as the client will need to dynamically load code from the server, it will use the address specified on the server i.e. 192.168.1.1 and a security exception will be raised.

We also need an HTML page from which the Applet will be launched:

<HTML>
<HEAD>
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=windows-1252">
<TITLE>
HTML Test Page
</TITLE>
</HEAD>
<BODY>
Here is the demo Applet for EJB access coming below...<BR>

<!--"CONVERTED_APPLET"-->
<!-- CONVERTER VERSION 1.3 -->
<SCRIPT LANGUAGE="JavaScript"><!--
    var _info = navigator.userAgent; var _ns = false;
    var _ie = (_info.indexOf("MSIE") > 0 && _info.indexOf("Win") > 0 && _info.indexOf("Windows 3.1") < 0);
//--></SCRIPT>
<COMMENT><SCRIPT LANGUAGE="JavaScript1.1"><!--
    var _ns = (navigator.appName.indexOf("Netscape") >= 0 && ((_info.indexOf("Win") > 0 && 
               _info.indexOf("Win16") < 0 && java.lang.System.getProperty("os.version").indexOf("3.5") < 0) 
               || (_info.indexOf("Sun") > 0) || (_info.indexOf("Linux") > 0)));
//--></SCRIPT></COMMENT>

<SCRIPT LANGUAGE="JavaScript"><!--
    if (_ie == true) document.writeln('<OBJECT classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" WIDTH = 400 
                     HEIGHT = 100 NAME = "AppletEjbCaller"
                     codebase="http://java.sun.com/products/plugin/1.3/jinstall-13-win32.cab#Version=1,3,0,0">
                     <NOEMBED><XMP>');
    else if (_ns == true) document.writeln('<EMBED type="application/x-java-applet;version=1.3"  
                          CODE = "org.jboss.docs.appletclient.applet.AppletEjbCaller" CODEBASE = "." 
                          ARCHIVE = "AppletClient.jar, jboss-client.jar, jboss-j2ee.jar, jbosssx-client.jar,
                          jnp-client.jar, jndi.jar, jaas.jar" NAME = "AppletEjbCaller" WIDTH = 400 HEIGHT = 100  
                          scriptable=false pluginspage="http://java.sun.com/products/plugin/1.3/plugin-install.html">
                          <NOEMBED><XMP>');
//--></SCRIPT>
<APPLET  CODE = "org.jboss.docs.appletclient.applet.AppletEjbCaller" CODEBASE = "." ARCHIVE = "AppletClient.jar, 
jboss-client.jar, jboss-j2ee.jar, jbosssx-client.jar, jnp-client.jar, jndi.jar, jaas.jar" WIDTH = 400 
HEIGHT = 100 NAME = "AppletEjbCaller"></XMP>
<PARAM NAME = CODE VALUE = "org.jboss.docs.appletclient.applet.AppletEjbCaller" >
<PARAM NAME = CODEBASE VALUE = "." >
<PARAM NAME = ARCHIVE VALUE = "AppletClient.jar, jboss-client.jar, jboss-j2ee.jar, jbosssx-client.jar,
jnp-client.jar, jndi.jar, jaas.jar" >
<PARAM NAME = NAME VALUE = "AppletEjbCaller" >
<PARAM NAME="type" VALUE="application/x-java-applet;version=1.3">
<PARAM NAME="scriptable" VALUE="false">

</APPLET>

</NOEMBED></EMBED></OBJECT>


<!--
<APPLET CODE = "org.jboss.docs.appletclient.applet.AppletEjbCaller" CODEBASE = "." ARCHIVE = "AppletClient.jar, 
jboss-client.jar, jboss-j2ee.jar, jbosssx-client.jar, jnp-client.jar, jndi.jar, jaas.jar" 
WIDTH = 400 HEIGHT = 100 NAME = "AppletEjbCaller">


</APPLET>
-->
<!--"END_CONVERTED_APPLET"-->



</BODY>
</HTML>

This HTML file has been transformed, from a standard HTML file with a standard <applet> tag definition by the SUN HTML converter for their Java plugin. This tools reads HTML files and replaces standard applet definition by browser specific declarations that will call the Java Plugin at runtime instead of the embedded JVM.

You then need to:

  • deploy our stateless session bean

  • copy the AppletEjbCaller.jar, jboss-client.jar, jboss-j2ee.jar, jbosssx-client.jar, jnp-client.jar, jndi.jar, jaas.jar (found in the Jboss/client folder) and the HTML file in a directory and share it for web access.

You should now be able to use your applet to access your bean.

While building the provided example, if the JBoss distribution is correctly referenced in the build script, the build process will automatically deploy the bean in JBoss and copy the Applet JAR and HTML file to the JBoss client folder.

Signed client Applet

One way to easily use Applets for EJB access is to circumvents the sandbox! This can be done by signing the Applet and all code it uses. When downloaded from a browser, it indicates to the user that this Applet is signed by a particular entity and ask for the permission to suppress the sandbox restriction.

The main problem with Applet signing is that almost each JVM vendor/browser requires its own method! For this reason, we will concentrate on the JDK 1.3 plugin from SUN that can be plugged in any browser on many platforms. Thus, use of signed Applets is more interesting in a controlled environment such as an intranet.

Signing an Applet

To sign an Applet, you first need to obtain a valid certificate. There are mainly two ways:

  1. Buy a code signing certificate from a recognised Java Plugin company (Thawtes or Verisign for example).

    Note

    To get the list of the recognised emitters for a particular JDK Plugin, you can run this command:

    keytool -keystore KEY_STORE -list

    where KEY_STORE represent your JDK default keystore, generally available in %JDK%/lib/security/cacerts.

    When asked for a password, the default value is "changeit".

  2. Generate your own certificate. For this, you can run this command:

    keytool -keystore KEY_STORE -genkey
    You will then be prompted for your key information. You then need to insert this key in the key store of all computers that will be used to access the Applet. This can be done by this command:
    keytool -keystore KEY_STORE -import -alias ANY_NAME -file YOUR_CERT_FILENAME

As you can see it, if you can afford a certificate from a recognised emitter, it is worth the pain.

Note

The Java plugin security documentation states that, on WIN32 systems, the JVM will also look in Windows keystore for recognised certificates emitters. Thus it would also be possible to get a certificate from on of the Internet Explorer emitters or add you own key to Windows keystore (which is just a matter of double-clicking on a certificate file). While this has been true for the first release of the JDK 1.3, future revision (1.3_001 and 1.3_002) have lost this capability without the documentation being updated. Recent feedback seems to indicate that this feature is again supported in release 1.3.1 of the Java Plugin.

Next, you need to sign your JARs. Not only your Applet Jar, but all JARs that are being used by your Applet. Consequently, all JBoss JARs mentioned in the first part (jboss-client.jar, jboss-j2ee.jar, jbosssx-client.jar, jnp-client.jar, jndi.jar, jaas.jar) also need to be signed. You may also group all these JARs in a single JAR and thus sign only the resulting JAR.

Signing a JAR can be done by executing the following command:

jarsigner -keystore KEY_STORE -genkey -storepass STORE_PASS JAR_FILE KEY_ALIAS_NAME

Or, if using ANT:

<signjar jar="${build.lib.dir}/monitron_Applet_client_bean.jar" keystore="${key.store.db}" alias="${key.store.alias}" storepass="${key.store.pw}"/>

Everytime the JAR is re-generated, it needs to be signed again. As the signing operation can be quite time consuming, it is generally better to first sign JBoss JAR and then only sign your own JAR(s). Thus, JBoss JARs need only be signed once.

Наши друзья