June 08, 2011

Secure SSL EJB Communication with GlassFish

Introduction

The purpose of this article is to document how to establish secure SSL communication between a GlassFish EJB server and a GlassFish WEB server using keystore certificates with GlassFish 3.0.1.

Starting with Java Enterprise Edition 5, EJB technology changed dramatically.  Building on ideas introduced by Spring and other frameworks, the new EJB specifications created a new easy to use standard.  Applications can take advantage of the technology of the Enterprise Server instead of relying on 3rd party technologies.  The EJB container allows easy access to both local and remote Enterprise Java Beans (EJB). Clients such as a Servlets, can use dependency injection to get instances of these EJBs with no code differences using local vs. remote beans.

However, using remote Enterprise Java Beans results in network traffic between the Enterprise server hosting the EJBs and the client application using the EJBs.  With network traffic comes the possibility for security problems so the goal is to have the EJB communication secured with SSL.

This article will be a cheat sheet on the configuration.  The cheat sheet will provide the relevant information but it will be up to you to integrate it into your projects.  Subsequent parts will step through creating and configuring a real working example.

Cheat Sheet

EJB


Create a @Remote interface for your EJB
A @Remote interface for you EJB defines the methods which are available to remote clients accessing the remote EJB

@Remote
public interface AccountService {    
  public Account findAccount(int id);
}

Create a @Stateless implementation of the @Remote interface for your EJB
An implementation may be @Stateful or @Stateless but in this example create a @Stateless implementation of the remote interface. 
@Stateless
public class AccountServiceBean implements AccountService
{
    @Override
    @PermitAll
    public Account findAccount(int id)
   {
        System.out.println(
          String.format("ENTER findAccount(%d)",id)
        );
        Account account = new Account();
        account.setAccountId(id);
        return account;
    }
}

Create a sun-ejb-jar.xml file which configures the use of SSL for your EJB
The sun-ejb-jar.xml file is a GlassFish specific configuration file responsible for configuring EJBs.  Typically the EJBs are compiled into a JAR and then the JAR put into an EAR.  The sun-ejb-jar.xml file is saved in the META-INF directory of the JAR.
The sun-ejb-jar.xml file should contain an <ejb> configuration for each EJB which needs secure SSL communication access.
Except for the <ejb-name> value, all the rest of the values should remain the same. These were the combination of values I found to work properly. 
<!DOCTYPE sun-ejb-jar PUBLIC "-//Sun Microsystems, Inc.//
DTD GlassFish Application Server 3.0 EJB 3.1//EN"
"http://www.sun.com/software/appserver/dtds/sun-ejb-jar_3_1-0.dtd">
<sun-ejb-jar>
  <enterprise-beans>
    <ejb>
      <ejb-name>
        AccountServiceBean
      </ejb-name>
      <ior-security-config>
        <transport-config>
          <integrity>
            required
          </integrity>
          <confidentiality>
            required
          </confidentiality>
          <establish-trust-in-target>
            SUPPORTED
          </establish-trust-in-target>
          <establish-trust-in-client>
            REQUIRED
          </establish-trust-in-client>
        </transport-config>
        <sas-context>
          <caller-propagation>
            supported
          </caller-propagation>
        </sas-context>
      </ior-security-config>
    </ejb>
  </enterprise-beans>
</sun-ejb-jar>
 

Create a JAR file for your EJB
When you create the JAR file for your EJB, the directory structure must be:
MyBeans.jar
  /META-INF
    sun-ejb-jar.xml
  /com
    /mycompany
      *.class

WAR


Create a Servlet for your WAR
Create a Servlet which uses dependency injection to get an instance of the EJB.  Remember, from the Servlet's point of view it will only be dealing with a local JNDI lookup name and the @Remote interface for the EJB. Beyond this, the Servlet knows no other details. 
The @EJB lookup is a local JNDI lookup name.  This name only exists within the namespace of this WAR.  The web.xml and sun-web.xml files configure how this local JNDI lookup maps to a real EJB instance.  I like to use local lookup names because when the WAR is configured properly and the lookup succeeds I am confident the code looked up the correct thing.  The alternative is auto-lookup magic and not knowing how the resource is found and injected is not a good thing.
@WebServlet(
    name="AccountServlet"
  , urlPatterns= {"/account"}
)
public class AccountServlet extends HttpServlet
{
    // Remember, this is a
    //
    //  !! LOCAL JNDI LOOKUP NAME !!
    //
    // and this lookup name is only
    // used by this WAR.  How this
    // local lookup name maps to a
    // real EJB instance is configured
    // in web.xml and sun-web.xml
    @EJB(lookup="java:comp/env/ejb/Sam")
    AccountService accountService;
}
 


Create a web.xml for your WAR
The web.xml file must contain an <ejb-ref> element.  This element sets 2 values.
  1. The local JNDI lookup name your WAR file uses when trying to find the EJB
  2. The fully-qualified name of the @Remote EJB interface.
You will need an <ejb-ref> element for every local JNDI lookup you WAR performs. 
<?xml version="1.0" encoding="UTF-8"?>
<web-app 
  xmlns:xsi=
    "
http://www.w3.org/2001/XMLSchema-instance
  xmlns=
    "
http://java.sun.com/xml/ns/javaee
  xmlns:web=
    "
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
  xsi:schemaLocation=
    "
http://java.sun.com/xml/ns/javaee
    
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd
  id="WebApp_ID" 
  version="2.5"

  <ejb-ref>
    <ejb-ref-name>
      ejb/Sam
    </ejb-ref-name>   
    <remote>
      org.ferris.ejb.account.AccountService
    </remote>
  </ejb-ref>
</web-app>

 
Create a sun-web.xml for your WAR
The sun-web.xml file is a GlassFish specific configuration file for your WAR.  Use the sun-web.xml file to map the local JNDI lookup the WAR with the real location of the EJB.  Having the WAR code only know about a local JNDI lookup makes this mapping easy and also allows for easy change of configuration between different environments (if needed).  
Some notes about the the value for <jndi-name>  below:
  1. It is displayed on multiple lines but in reality it should all be on 1 line! 
  2. Don't miss any of the ":", "/", or "!" characters
  3. [SERVER_NAME] - This is the name of the GlassFish server where the @Remote EJBs are deployed.
  4. [EAR_NAME] - This is the name of the EAR application, which is typically the name of the EAR file.  Suppose you deploy the "MyBeans.ear" file to GlassFish.  By default GlassFish will use "MyBeans" as the name of the EAR application.  This value can be overridden or changed, but typically it is the name of the EAR file.
  5. [JAR_NAME] - This is the name of the JAR file inside the EAR file which holds the beans.  Suppose you have an  "account.jar" file inside the "MyBeans.ear" file which holds the EJB classes for account information.  By default, GlassFish will use "account" as the module name.  This value can be overridden or changed, but typically it is the name of the JAR file.
  6. [BEAN_NAME] - This is of course the bean name.  It should match what is in the sun-ejb-jar.xml configuration file deployed with the beans.
  7. [FULLY_QUALIFIED_REMOTE_INTERFACE] - This value speaks for itself.  It is the fully qualified @Remote interface of the EJB.
<!DOCTYPE
  sun-web-app PUBLIC "-//Sun Microsystems, Inc.//DTD
  GlassFish Application Server 3.0 Servlet 3.0//EN"
  "
http://www.sun.com/software/appserver/dtds/sun-web-app_3_0-0.dtd"
>
<sun-web-app>
  <ejb-ref>
 
    <!--
     | local JNDI lookup name your code uses
     -->
    <ejb-ref-name>ejb/Sam</ejb-ref-name>
 
    <!--
     | The jndi-name value is the same
     | for both secure and non-secure
     | configuration.  The sun-ejb-jar.xml
     | file on the EJB server determines
     | if the two GlassFish instances should
     | establish a secure communications
    -->
    <jndi-name>
      corbaname
      :     
      iiop
      :
      [SERVER_NAME]
      :
      3700#java
      :
      global
      /
      [EAR_NAME]
      /
      [JAR_NAME]
      /
      [BEAN_NAME]
      !
      [FULLY_QUALIFIED_REMOTE_INTERFACE]
    </jndi-name>
  </ejb-ref>
</sun-web-app>


GlassFish Keystore Key Exchange

Secure EJB communication between two GlassFish servers is easiest when the servers can exchange certificates and automatically trust each other.  To do this, the certificates in the Keystore (keystore.jks) of each GlassFish server must be exported and then imported into the Truststore (cacerts.jks) of the other GlassFish server.

Export Cert From keystore.jks


Command for GlassFish EJB Server 
Execute the following command on the GlassFish EJB Server to export the GlassFish EJB Server certificate. 
C:>cd C:\glassfish\3.1\glassfish\domains\domain1\config
C:>keytool.exe -export -rfc -alias s1as -keystore keystore.jks -file GlassFishEJBServer.cer
The default password for keystore.jks is "changeit" 
 Command for GlassFish WEB Server 
Execute the following command on the GlassFish WEB Server to export the GlassFish WEB Server certificate.  
C:>cd C:\glassfish\3.1\glassfish\domains\domain1\config
C:>keytool.exe -export -rfc -alias s1as -keystore keystore.jks -file GlassFishWEBServer.cer
The default password for keystore.jks is "changeit"  



Exchange Cert Files
Assume you have exported both certificates and the names of the files are GlassFishEJBServer.cer and GlassFishWEBServer.cer.  Copy, sftp, or use whatever file transfer protocol you need to exchange the files between the servers.

Import Cert Into cacerts.jks


Command for GlassFish EJB Server
Execute the following command on the GlassFish EJB Server to import the GlassFish WEB Server certificate.
C:>cd C:\glassfish\3.1\glassfish\domains\domain1\config
C:>keytool.exe -import -noprompt -trustcacerts -alias glassfishwebserver -file GlassFishWEBServer.cer -keystore cacerts.jks
The default password for cacerts.jks is "changeit" 
Command for GlassFish WEB Server
Execute the following command on the GlassFish WEB Server to import the GlassFish EJB Server certificate.
C:>cd C:\glassfish\3.1\glassfish\domains\domain1\config
C:>keytool.exe -import -noprompt -trustcacerts -alias glassfishejbserver -file GlassFishEJBServer.cer -keystore cacerts.jks
The default password for cacerts.jks is "changeit" 



Conclusion 

The purpose of this article is to document how to establish secure SSL communication between an GlassFish EJB server and a GlassFish WEB server using keystore certificates with GlassFish 3.0.1.  This purpose was accomplished by first documenting the configuration of the EJB module, then documenting the configuration of the WEB module, then finally documenting how to exchange certificates between the two servers.  Although this article leaves out details on how to actually implement these steps, they should given enough information to integrate into your code/application/server specific setup.  Of course make comments or contact me with any questions.