Author:[email protected]>
<
This document applies to JBoss release 2.2.2 and later. If your using an earlier version of JBoss upgrade to the 2.2.2 version.
This document describes the setting up secured access to JBoss hosted EJBs and web applications for the standard declarative J2EE security model. It should be sufficient to allow you to configure a simple security setup for testing and also give you a good start to being able to integrate your own custom security infrastructure into JBoss. For a more detailed description of the JBossSX framework see the JBossSX chapter.
The security model in JBoss is based on the server container architecture's pluggable method interceptors and the fact that the container factory always inserts the security interceptor (org.jboss.ejb.plugins.SecurityInterceptor). The SecurityInterceptor delegates the tasks of principal authentication and principal role mapping to two different security interfaces: org.jboss.security.EJBSecurityManager and org.jboss.security.RealmMapping. JBoss includes a number of sample implementations of both interfaces which can be found in the org.jboss.security.plugins.samples package.
The default security implementation that comes pre-configured consists of a JMX service bean and a JAAS based implementation of both interfaces. The JMX bean is org.jboss.security.plugins.JaasSecurityManagerService and the security interfaces implementation is org.jboss.security.plugins.JaasSecurityManager. This document will focus on setting up the JaasSecurityManager via the JaasSecurityManagerService for a trivial stateless session bean. Once you can perform the steps documented to secure the example bean, you should be able to introduce your own production ready security using this example as a template.
Ok, so you know that every EJB container in JBoss includes a SecurityInterceptor that delegates its security checks to a security manager implementation. How do you choose which implementations a given container uses? You specify this information via the jboss deployment descriptor.
The JBoss deployment descriptor is the JBoss application specific deployment configuration file. It describes implementation behavior that is outside of the EJB spec ejb-jar.xml deployment descriptor. The standardjboss.xml version of the file is located in ${jboss_home}/conf/conf_name where ${jboss_home} is the directory into which you have installed the JBoss distribution and conf_name is the specific runtime configuration that you specify to the run.sh or run.bat script when starting the server. The default value for conf_name is "default". The standardjboss.xml specifies the global configuration default values. You can also specific ejb-jar or j2ee-ear specific jboss.xml descriptors that override specific or all configuration properties as appropriate for your application. There are a quite a few configurable properties that can be set in the file, but all are optional. For all of the possible configuration elements and their details see the jboss.dtd. We are only concerned with the three security specific elements:
The security-domain element specifies an implementation of both the org.jboss.security.RealmMapping and org.jboss.security.EJBSecurityManager interfaces to use for all J2EE deployment units in the ear or ejb-jar. The value is specified as the JNDI name where the object is located. Hence, the security-domain is like a JMS TopicConnectionFactory in that it is accessed via a JNDI name whose setup is a managed process.
The role-mapping-manager element specifies the implementation of the org.jboss.security.RealmMapping interface that is to be used by the container SecurityInterceptor. The value is specified as the JNDI name where the object is located. As far as the container configuration is concerned, an implementation of org.jboss.security.RealmMapping exists in the JBoss server JNDI namespace and role-mapping-manager element provides the location.
The authentication-module element specifies the implementation of the org.jboss.security.EJBSecurityManager interface that is to be used by the container SecurityInterceptor. The value is specified as the JNDI name to where the object is located, just like the role-mapping-manager.
A sample jboss.xml descriptor is:
<?xml version="1.0"?> <jboss> <!-- All bean containers use this security manager by default --> <security-domain>java:/jaas/example1</security-domain> <container-configurations> <!-- Override the role mapping function from that of the security-domain setting for stateless session beans --> <container-configuration> <!-- Use the standardjboss.xml container-name so we only have to specify the elements we want to override --> <container-name>Standard Stateless SessionBean</container-name> <role-mapping-manager>java:/jaas/session-roles</role-mapping-manager> </container-configuration> </container-configurations> </jboss>
Establishes a global security manager via thesecurity-domain element. | |
Overrides the global security manager role mapping function for stateless session beans. |
Here we are assigning a global security manager for all beans to the the object located at java:/jaas/example1 and we are setting a different role mapping manager for the “Standard Stateless SessionBean” container. This means that any stateless session beans bundled in the ear or jar will use the RealmMapper located at java:/jaas/session-roles rather the the security-domain element setting. We will see the reason for choosing JNDI names of the form java:/jaas/XXX over the next couple of sections.
The jboss-web.xml deployment descriptor is the JBoss application specific deployment used to set the security manager and JNDI bindings for web applications. Like the jboss.xml it uses a security-domain element to declare the JNDI name of the security manager that will perform authentication and authorization of users attempting to access secured content. An example jboss-web.xml descriptor that uses the same security manager used to secure EJBs is given in Figure 11.9.
So we have setup the container configuration security elements to specify the JNDI names where the desired RealmMapping and EJBSecurityManager implementations are to be obtained from. Now the question is how to bind implementations into the JBoss server JNDI namespace. The answer is to create a JMX mbean that creates and binds the desired implementations at server startup. The JaasSecurityManagerService is an mbean that has been written that we will use to perform the required setup.
To configure the JaasSecurityManagerService, open the ${jboss_home}/conf/default/jboss.jcml file and look for an entry like:
<!-- JAAS security manager and realm mapping --> <mbean code="org.jboss.security.plugins.JaasSecurityManagerService" name="Security:name=JaasSecurityManager"> <attribute name="SecurityManagerClassName">org.jboss.security.plugins.JaasSecurityManager</attribute> <attribute name="SecurityProxyFactoryClassName">org.jboss.security.SubjectSecurityProxyFactory</attribute> </mbean>
If it is commented out or does not exist, uncomment or add the entry. The JaasSecurityManagerService service creates a reference to a JNDI Context at java:/jaas that lazily binds instances of org.jboss.security.plugins.JaasSecurityManager under java:/jaas as they are requested via JNDI. The details of how this happens are not important(if they are to you, look at the code). All we care about is that with the JaasSecurityManagerService setup, any lookup on the JBoss server JNDI InitialContext using a name of the form java:/jaas/xyz results in an object of type org.jboss.security.plugins.JaasSecurityManager that has the name xyz. Translated to code, this means:
InitialContext ctx = new InitialContext(); JaasSecurityManager jsm1 = (JaasSecurityManager) ctx.lookup("java:/jaas/xyz"); String securityDomain = jsm1.getSecurityDomain(); // securityDomain == "xyz"
where jsm1 is an instance of JaasSecurityManager that was created using the name "xyz". We are using this feature to bind a single instance of JaasSecurityManager for use as both the RealmMapping and EJBSecurityManager implementations in the preceeding jboss.xml descriptor. We can do this because JaasSecurityManager implements both interfaces. Now we need to know how we can actually authenticate users and specify the roles/identies they possess with a JaasSecurityManager.
As you would expect, the JaasSecurityManager uses JAAS (Java Authentication and Authorization Service) to implement both the user authentication and role mapping function of the RealmMapping and EJBSecurityManager interfaces. It does this by creating a JAAS Subject using the javax.security.auth.login.LoginContext mechanism. When the JaasSecurityManager needs to authenticate a user, it does a JAAS login using the following programmatic steps:
Principal principal = ... passed in by SecurityInterceptor; Object credential = ... passed in by SecurityInterceptor; /* Access the security domain to which the security manager is bound. This is the xyz component of java:/jaas/xyz name used when defining the security-domain or role-mapping-manager config elements. */ String name = getSecurityDomain(); CallbackHandler handler = new org.jboss.security.plugins.SecurityAssociationHandler(); handler.setSecurityInfo(principal, credential); LoginContext lc = new LoginContext(name, handler); // Validate principal, credential using the LoginModules configured for 'name' lc.login(); Subject subject = lc.getSubject(); Set subjectGroups = subject.getPrincipals(Group.class); // Get the Group whose name is 'Roles' Group roles = getGroup(subjectGroups, "Roles");
If you are familiar JAAS, you'll see that the name that was used in the creation of the JaasSecurityManager correlates with the LoginContext Configuration name. The JAAS LoginContext object looks to a configuration object that is made up of named sections that describe the LoginModules that need to be executed in order to perform authentication. This abstraction allows the authentication API to be independent of a particular implementation. The authentication of users and the assignment of user roles comes down to implementing a javax.security.auth.spi.LoginModule and creating login configuration entry that correlates with the JaasSecurityManager name. There exist a number of LoginModule implementations in the org.jboss.security.auth.spi package. We are going to go over the use of the simple UsersRolesLoginModule as well as the production read DatabaseServerLoginModule to demonstrate how to configure LoginModules to work with the JaasSecurityManager. You can choose from among the existing LoginModule implementations the one that best integrates with your security environment, or implement you own and then configure it using the same steps we will use.
The UsersRolesLoginModule class is a simple properties file based implemention that uses two Java Properties(users.properties and roles.properities) to perform authentication and role mapping respectively.
The users.properties file is a java properties formatted file that specifies the username to password mapping. Its format is:
username1=password1 username2=password2 ...with one entry per line.
The roles.properties file is a java properties formatted file that specifies the username to role(s) mapping. Its format is:
username1=role1[,role2,...] username2=role1 ...with one entry per line. If a user has multiple roles they are specified using a comma separated list. You can also specify groups of roles using a syntax like:
username1.GroupName1=role1[,role2,...] username2.GroupName2=role1 ...When no GroupName is specified a group name of 'Roles' is implied.
By default JAAS uses a LoginModule configuration file to describe which LoginModule instances need to be executed during a login. The default config for the JBoss server is ${jboss_home)/conf/default/auth.conf. The syntax is:
name { login_module_class_name (required|optional|...) [options] ; };See the JAAS documentation for the complete syntax description. An example auth.conf file with two configurations is given below in Figure 11.10.
Figure 11.10. The JBoss Server JAAS Login Config File ($jboss_home/conf/default/auth.conf)
example1 { // A properties file LoginModule that supports CallerPrincipal mapping org.jboss.security.auth.spi.UsersRolesLoginModule required ; }; example2 { /* A JDBC based LoginModule LoginModule options: dsJndiName: The name of the DataSource of the database containing the Principals, Roles tables principalsQuery: The prepared statement query equivalent to: "select Password from Principals where PrincipalID=?" rolesQuery: The prepared statement query equivalent to: "select Role, RoleGroup from Roles where PrincipalID=?" */ org.jboss.security.auth.spi.DatabaseServerLoginModule required dsJndiName="java:/DefaultDS" principalsQuery="select Password from Principals where PrincipalID=?" rolesQuery="select Role, RoleGroup from Roles where PrincipalID=?" ; };
This indicates that the UsersRolesLoginModule we want to use is setup for the configuration named 'example1'. This name also matches name of the security domain portion of the JNDI name java:/jaas/example1 used as the security-domain element in the sample jboss.xml file shown in Figure 11.8. The correlation between the security-domain element value and the login config file entry determines which LoginModules executed by the JaasSecurityManager to perform authentication and authorization. When a user attempts to execute methods on EJBs secured under the java:/jaas/example1 security domain, the user will be authenticated against the UsersRolesLoginModule since this the the LoginModule configured under the example1 name in the server auth.conf file.
There is also a client side version of the auth.conf that is used by the client connecting to JBoss. It is located in ${jboss_home}/client/auth.conf and the default version contents are given in Figure 11.11. The key entry here is the 'other' entry that contains the 'org.jboss.security.ClientLoginModule required;' setting.
Figure 11.11. The JBoss Client JAAS Login Config File ($jboss_home/client/auth.conf)
srp { // Example client auth.conf for using the SRPLoginModule org.jboss.srp.jaas.SRPLoginModule required password-stacking="useFirstPass" principalClassName="org.jboss.security.SimplePrincipal" srpServerJndiName="SRPServerInterface" debug=true ; // jBoss LoginModule org.jboss.security.ClientLoginModule required password-stacking="useFirstPass" ; // Put your login modules that need jBoss here }; other { // Put your login modules that work without jBoss here // jBoss LoginModule org.jboss.security.ClientLoginModule required; // Put your login modules that need jBoss here };
We have now touched on all of the JBoss security related elements we need to configure to secure the deployment of EJBs and web applications. Let's now put together two simple session beans and a servlet that we will secure to demonstrate the use what we have gone over.
The following figures give the code listings for the the home, remote and bean classes for the simple stateless and stateful session beans we are going to secure, along with a simple client that accesses instances of the session beans. Also shown is a simple servlet that accesses one of the EJBs. The complete source code along with deployment descriptors and an Ant build script is available JAAS-Howto Files.
Figure 11.12. The Session Beans Remote Interface
import javax.ejb.*; import java.rmi.*; public interface Session extends EJBObject { public String echo(String arg) throws RemoteException; public void noop() throws RemoteException; }
Figure 11.13. The Session Beans Home Interface
import javax.ejb.*; import java.rmi.*; public interface SessionHome extends EJBHome { public Session create() throws RemoteException, CreateException; }
Figure 11.14. The Stateless Session Bean
import java.rmi.RemoteException; import java.security.Principal; import javax.ejb.*; /** @ejbHome: SessionHome @ejbRemote: Session */ public class StatelessSessionBean implements SessionBean { private SessionContext sessionContext; public void ejbCreate() throws CreateException { System.out.println("StatelessSessionBean.ejbCreate() called"); } public void ejbActivate() { System.out.println("StatelessSessionBean.ejbActivate() called"); } public void ejbPassivate() { System.out.println("StatelessSessionBean.ejbPassivate() called"); } public void ejbRemove() { System.out.println("StatelessSessionBean.ejbRemove() called"); } public void setSessionContext(SessionContext context) { sessionContext = context; } public String echo(String arg) { System.out.println("StatelessSessionBean.echo, arg="+arg); Principal p = sessionContext.getCallerPrincipal(); System.out.println("StatelessSessionBean.echo, callerPrincipal="+p); return arg; } public void noop() { System.out.println("StatelessSessionBean.noop"); Principal p = sessionContext.getCallerPrincipal(); System.out.println("StatelessSessionBean.noop, callerPrincipal="+p); } }
Figure 11.15. The Stateful Session Bean
import java.rmi.RemoteException; import java.security.Principal; import javax.ejb.*; /** @ejbHome: SessionHome @ejbRemote: Session */ public class StatefulSessionBean implements SessionBean { private SessionContext sessionContext; public void ejbCreate() throws CreateException { System.out.println("StatefulSessionBean.ejbCreate() called"); } public void ejbActivate() { System.out.println("StatefulSessionBean.ejbActivate() called"); } public void ejbPassivate() { System.out.println("StatefulSessionBean.ejbPassivate() called"); } public void ejbRemove() { System.out.println("StatefulSessionBean.ejbRemove() called"); } public void setSessionContext(SessionContext context) { sessionContext = context; } public String echo(String arg) { System.out.println("StatefulSessionBean.echo, arg="+arg); Principal p = sessionContext.getCallerPrincipal(); System.out.println("StatefulSessionBean.echo, callerPrincipal="+p); return arg; } public void noop() { System.out.println("StatefulSessionBean.noop"); Principal p = sessionContext.getCallerPrincipal(); System.out.println("StatefulSessionBean.noop, callerPrincipal="+p); } }
Figure 11.16. The ejb-jar Deployment Descriptor
<?xml version="1.0" encoding="UTF-8"?> <!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> <display-name>SecurityTests</display-name> <enterprise-beans> <session> <description>A trival stateless session echo bean</description> <ejb-name>StatelessSession</ejb-name> <home>SessionHome</home> <remote>Session</remote> <ejb-class>StatelessSessionBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> </session> <session> <description>A trival stateful session echo bean</description> <ejb-name>StatefulSession</ejb-name> <home>SessionHome</home> <remote>Session</remote> <ejb-class>StatefulSessionBean</ejb-class> <session-type>Stateful</session-type> <transaction-type>Container</transaction-type> </session> </enterprise-beans> <assembly-descriptor> <security-role> <role-name>Echo</role-name> </security-role> <method-permission> <role-name>Echo</role-name> <method> <ejb-name>StatelessSession</ejb-name> <method-name>*</method-name> </method> <method> <ejb-name>StatefulSession</ejb-name> <method-name>*</method-name> </method> </method-permission> <method-permission> <role-name>Coder</role-name> <method> <ejb-name>StatefulSession</ejb-name> <method-name>create</method-name> </method> <method> <ejb-name>StatefulSession</ejb-name> <method-name>remove</method-name> </method> <method> <ejb-name>StatefulSession</ejb-name> <method-name>noop</method-name> </method> </method-permission> </assembly-descriptor> </ejb-jar>
Figure 11.17. The jboss.xml Deployment Descriptor
<?xml version="1.0" encoding="UTF-8"?> <jboss> <!-- All bean containers use this security manager by default --> <security-domain>java:/jaas/example1</security-domain> <enterprise-beans> <session> <ejb-name>StatelessSession</ejb-name> <jndi-name>example1/StatelessSession</jndi-name> </session> <session> <ejb-name>StatefulSession</ejb-name> <jndi-name>example1/StatefulSession</jndi-name> </session> </enterprise-beans> </jboss>
import java.io.IOException; import javax.naming.InitialContext; import javax.rmi.PortableRemoteObject; import javax.security.auth.callback.*; import javax.security.auth.login.*; /** A simple session client that access the two secured EJBs as the user passed in on the command line. @author [email protected] @version $Revision: 3.0 $ */ public class SessionClient { static class AppCallbackHandler implements CallbackHandler { private String username; private char[] password; public AppCallbackHandler(String username, char[] password) { this.username = username; this.password = password; } public void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof NameCallback) { NameCallback nc = (NameCallback)callbacks[i]; nc.setName(username); } else if (callbacks[i] instanceof PasswordCallback) { PasswordCallback pc = (PasswordCallback)callbacks[i]; pc.setPassword(password); } else { throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } } } public static void main(String args[]) throws Exception { if( args.length != 3 ) throw new IllegalArgumentException("Usage: username password example"); String name = args[0]; char[] password = args[1].toCharArray(); String example = args[2]; System.out.println("+++ Running SessionClient with username="+name+", password="+args[1]+", example="+example); try { AppCallbackHandler handler = new AppCallbackHandler(name, password); LoginContext lc = new LoginContext("TestClient", handler); System.out.println("Created LoginContext"); lc.login(); } catch (LoginException le) { System.out.println("Login failed"); le.printStackTrace(); } try { InitialContext iniContext = new InitialContext(); SessionHome home = (SessionHome) iniContext.lookup(example+"/StatelessSession"); System.out.println("Found StatelessSessionHome"); Session bean = home.create(); System.out.println("Created StatelessSession"); System.out.println("Bean.echo('Hello') -> "+bean.echo("Hello")); bean.remove(); } catch(Exception e) { e.printStackTrace(); } try { InitialContext iniContext = new InitialContext(); SessionHome home = (SessionHome) iniContext.lookup(example+"/StatefulSession"); System.out.println("Found StatefulSessionHome"); Session bean = home.create(); System.out.println("Created StatefulSession"); bean.noop(); System.out.println("Bean.noop() called"); System.out.println("Bean.echo('Hello') -> "+bean.echo("Hello")); bean.remove(); } catch(Exception e) { e.printStackTrace(); } } }
Figure 11.19. The Secure Servlet
import java.io.IOException; import java.io.PrintWriter; import java.security.Principal; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import Session; import SessionHome; /** A simple servlet that accesses the stateless session bean. @author [email protected] */ public class SecureEJBServlet extends HttpServlet { protected void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String echoMsg = null; try { InitialContext ctx = new InitialContext(); SessionHome home = (SessionHome) ctx.lookup("java:comp/env/ejb/SecuredEJB"); Session bean = home.create(); echoMsg = bean.echo("Hello"); } catch(Exception e) { throw new ServletException("Failed to call SecuredEJB.echo", e); } Principal user = request.getUserPrincipal(); response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println("<html>"); out.println("<head><title>SecureEJBServlet</title></head>"); out.println("<h1>SecureServlet Accessed</h1>"); out.println("<body><pre>You have accessed this servlet as user: "+user); out.println("The SecuredEJB.echo('Hello') returned: "+echoMsg); out.println("</pre></body></html>"); out.close(); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } }
Figure 11.20. The web-app Deployment Descriptor
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2_2.dtd"> <web-app> <!-- ### Servlets --> <servlet> <servlet-name>SecureServlet</servlet-name> <servlet-class>SecureEJBServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>SecureServlet</servlet-name> <url-pattern>/restricted/SecureServlet</url-pattern> </servlet-mapping> <!-- ### Security --> <security-constraint> <web-resource-collection> <web-resource-name>Restricted</web-resource-name> <description>Declarative security tests</description> <url-pattern>/restricted/*</url-pattern> <http-method>HEAD</http-method> <http-method>GET</http-method> <http-method>POST</http-method> <http-method>PUT</http-method> <http-method>DELETE</http-method> </web-resource-collection> <auth-constraint> <role-name>Echo</role-name> </auth-constraint> <user-data-constraint> <description>no description</description> <transport-guarantee>NONE</transport-guarantee> </user-data-constraint> </security-constraint> <login-config> <auth-method>BASIC</auth-method> <realm-name>JAAS Tutorial Servlets</realm-name> </login-config> <security-role> <description>A user allowed to invoke echo methods</description> <role-name>Echo</role-name> </security-role> <!-- ### EJB References (java:comp/env/ejb) --> <ejb-ref> <ejb-ref-name>ejb/SecuredEJB</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>SessionHome</home> <remote>Session</remote> </ejb-ref> </web-app>
Figure 11.21. The jboss-web.xml Deployment Descriptor
<?xml version="1.0" encoding="UTF-8"?> <jboss-web> <security-domain>java:/jaas/example1</security-domain> <ejb-ref> <ejb-ref-name>ejb/SecuredEJB</ejb-ref-name> <jndi-name>example1/StatelessSession</jndi-name> </ejb-ref> </jboss-web>
The session beans are trivial and both the stateless and stateful bean use the same home and remote interfaces. The client is also trivial except for the use of a JAAS LoginContext and CallbackHandler implementation. This is how a client establishes the username and password that is sent to JBoss. The servlet looks up the home interface of the StatelessSession bean and creates a Session instance. It then invokes echo on this and displays the user principal that accessed the servlet and the output of the Session.echo method. Now, finally let's put everything together and deploy the session beans and servlet as a J2EE ear.
This section details the procedure for building, deploying and testing a secured J2EE ear made up of the EJBs and servlet shown in the preceeding figures. I'll walk you through the build of two different versions of the ears using Ant and the build.xml file contained in the tutorial files bundle. The files bundle is available from JAAS-Howto Files.
Procedure 11.3. Deployment Steps
Download the JBoss/Tomcat bundle. Go toJBoss @ SourceForgeand download the JBoss-2.2.2/Tomcat-3.2.2 bundle or later version. The 2.2.2 version of JBoss is the first to ship with the integrated ejb/web security that is required for this tutorial example. Unpack the archive to create a JBoss-2.2.2_Tomcat-3.2.2 directory that contains jboss and tomcat subdirectories. I have downloaded and unpacked the archive into /tmp on my machine so the directory structure looks like that shown in Figure 11.22. The jboss.dist and servlet.jar labels in red will be used below to configure the Ant build script.
Download the tutorial files bundle. If you have not already, download the tutorial files from here: JAAS-Howto Files. Unpack the archive to create a jaas directory with the contents shown in Figure 11.23.
Build and deploy the tutorial ears.
Edit the build.xml to set the correct location of the jboss.dist and servlet.jar variables to the corresponding paths of your JBoss/Tomcat bundle distribution installation. See Figure 11.22for the items in the distribution to which jboss.dist and servlet.jar refer. For my installation of the distribution under /tmp as shown above, the correct settings are given in Figure 11.24.
Figure 11.24. JAAS Tutorial Files
<project name="JAAS Howto Build Script" default="ears" basedir="."> ... <!-- Override with your JBoss server dist location --> <property name="jboss.dist" value="/tmp/JBoss-2.2.2_Tomcat-3.2.2/jboss"/> <!-- Override with your web server servlet jar location --> <property name="servlet.jar" value="/tmp/JBoss-2.2.2_Tomcat-3.2.2/tomcat/lib/servlet.jar"/>
Download and install Ant1.3 or later. If you don't have a copy of Ant on your system download version 1.3 or later from the Apache site here.
Build and install the tutorial ears. From within the jaas directory that contains the Ant build.xml file, execute the following command:
This has created two ears(tutorial1.ear, tutorial2.ear) that contain the ejbs and servlet presented earlier and placed them into the JBoss server deploy directory. It has also replaced the contents of the JBoss server auth.conf file with the jaas/server_auth.conf file contents. This file is the same as that show in Figure 11.10.
Start the JBoss/Tomcat server. Go to the jboss/bin directory of your JBoss/Tomcat bundle installation and execute the run_with_tomcat.sh for *nix platforms and run_with_tomcat.bat for w32 platforms. A number of messages will be displayed on your console. Some of the key messages you should see are:
At this point you have the JBoss server running with the embeded Tomcat servlet engine and the two tutorial ears have been deployed.
Test access to the session bean
At this point the session beans are deployed and only users with a role of 'Echo' are allowed to execute all home and remote interface methods of the StatelessSession and StatefulSession beans. There is one user with a username 'scott' and a password 'echoman' that has this role. We have another user with a username 'stark' and a password 'javaman' with roles of 'Java' and 'Coder' that should not be able to execute any methods of the StatelessSession because he does not have the required 'Echo' role. He should be able to create and remove StatefulSession beans and execute the noop method since the role of 'Coder' is allowed these operations. Lets run through 4 test cases that confirm we have properly secured the EJBs and servlet using the UsersRolesLoginModule.
Run the SessionClient as user 'scott' with password 'echoman'. Do this by executing the following command:
Ok, so that succeeded as desired. Now we need to make sure that unauthorized users are actually denied access. This time run as username 'stark' with password 'javaman' by executing the following command:
This demonstrates that user 'stark' cannot execute any methods of the StatelessSession home or remote interfaces. Also demonstrated is that 'stark' can create a StatefulSession bean and invoke the noop method on it, but he cannot invoke the echo method which is correct. Now let's try username 'scott' with an invalid password to see if authentication fails:
All is working as expected using a Java client. Now we need to try to access the secure servlet and see that the same username and password we have been using to access the EJBs allows us access to the servlet. Since the servlet also calls the EJBs, we will see if the servlet user principal and credentials are passed correctly to the EJB. To try this, click on the this link: CLICK HEREand enter scott as the username and echoman as the password. You should see a page like that in the following figure.
The output on the server console due to the client accessing the web will look like the following:
The first set of error messages result from the web container trying to authenticate a 'null' username meaning an anonymous user. The JBossSecurityMgrRealm passes the null username and password onto the JBoss security manager rather than simply returning an unauthorized error in case the configured security manager wishes to treat a null username as a special user.
We have tested authenticating users based on the simple UsersRolesLoginModule module that reads the users.properties and roles.properties files bundled in the deployed ejb jar. This is convient for development, but it is not realistic of a production environment. In this section we will run through the same tests as the preceeding section, but we will be authenticating the users against a JDBC database using the DatabaseServerLoginModule.
Build the security database. The first thing we need to do is to create the database tables required by the DatabaseServerLoginModule. The DatabaseServerLoginModule expects a Principals table that contains the principal id to password mapping and a Roles table that contains the principal id to roles mapping. The psuedo-schema for the two tables is:
To create the security tables that contain the user and role data equivalent to that in the users and roles properties files, we need to run the BuildDatabase Java program. With the JBoss server still running, execute:
This creates a Principals and Roles tables with the indicated values in the Hypersonic database that is configured as the default in the standard JBoss distribution.
Run the SessionClient as user 'scott' with password 'echoman' using the DatabaseServerLoginModule to perform the authentication. Do this by executing the following command:
This time run as username 'stark' with password 'javaman' by executing the following command:
As before, this demonstrates that user 'stark' cannot execute any methods of the StatelessSession home or remote interfaces. Also demonstrated is that 'stark' can create a StatefulSession bean and invoke the noop method on it, but he cannot invoke the echo method which is correct. Now let's try username 'scott' with an invalid password to see if authentication fails: