Java EE Exercise

Part H: Securing the EJB Tier and RMI Interface

This exercise will step through the setup of security authentication and authorization for the EJB Tier and access through an RMI client. We will use simple credentials (username and password) and a simple credential and role store (property files) for the duration of the exercise. Once we have the application updated, integrated, and working with the simple approach -- more advance techniques (e.g., PKI certs, RDBMS or LDAP for credential storage) can be added without impacting the core of the application.

Objectives

Verify Server has Required Users and Roles

We will be using a built-in security domain that comes with the application server by default. The security domain is powered by a set of property files witin the JBOSS_HOME/standalone/configuration directory. Your server should already be setup with the required accounts and roles but look through the following steps for how that was done or if you ever want to create new accounts for your project(s).

  1. Verify if your application server has required users/roles defined that are required to complete the exercise.
    $ cat .../standalone/configuration/application-users.properties
    ...
    known=3745b3f6973383c9c11810c7b200b1f4
    user1=2dc3eacfed8cf95a4a31159167b936fc
    admin1=2ae76a0e3f0b615a6229c880555273b5
    ...
  2. If the above users (known, user1, and admin1) are missing, add them with the following command.
    • known - role: (no roles)
    • user1 - role: user
    • admin1 - role: admin,user (no space)
      $ /opt/jboss-as-7.1.1.Final/bin/add-user.sh 
      
      What type of user do you wish to add? 
       a) Management User (mgmt-users.properties) 
       b) Application User (application-users.properties)
      (a): b
      
      Enter the details of the new user to add.
      Realm (ApplicationRealm) : 
      Username : admin1
      Password : 
      Re-enter Password : 
      What roles do you want this user to belong to? (Please enter a comma separated list, or leave blank for none) : admin,user
      About to add user 'admin1' for realm 'ApplicationRealm'
      Is this correct yes/no? yes
      Added user 'admin1' to file '/opt/jboss-as-7.1.1.Final/standalone/configuration/application-users.properties'
      Added user 'admin1' to file '/opt/jboss-as-7.1.1.Final/domain/configuration/application-users.properties'
      Added user 'admin1' with roles admin,user to file '/opt/jboss-as-7.1.1.Final/standalone/configuration/application-roles.properties'
      Added user 'admin1' with roles admin,user to file '/opt/jboss-as-7.1.1.Final/domain/configuration/application-roles.properties'
  3. Look at the definition for the "other" security domain within the standalone.xml for your server. Notice the following.
    • authentication - contain login-modules used to authenticate users
    • login-module(s) - contain a specific strategy for authentication and a flag property about its significance to the overall authentication
      • optional - module is not required to succeed.
      • sufficient - module not required to succeed but if it does the authentication process will stop.
      • required - must succeed for authentication to be successful. Continues through other modules whether successful or not. This means that a downstream authenticator may overrule a success here with a later faliure (e.g., revocation).
      • requisite - must succeed for authentication to be successful. Stops going through other modules on failue.
    • RealmUsersRoles login-module
      • the users will be authenticated against a property file called application-users.properties
      • roles will be checked against a property file called application-roles.properties
      • there is no unauthenticatedIdentity defined at the application policy level, so we can specify this value as a part of our deployed application.
    • code - The code attribute in the login-module element is either the fully qualified class name of a custom login module or a short name for a LoginModule usually with the picketbox org.jboss.security.auth.spi package or jboss-as jboss.as.security extensions. Picketbox is a security project within JBoss who's use extends beyond JavaEE and use within JBoss.
    • (other information is harder to track down...)
      $ cd $JBOSS_HOME
      $ cat standalone/configuration/standalone.xml
      
      ...
      <security-domain name="other" cache-type="default">
          <authentication>
              <login-module code="Remoting" flag="optional">
                  <module-option name="password-stacking" value="useFirstPass"/>
              </login-module>
              <login-module code="RealmUsersRoles" flag="required">
                  <module-option name="usersProperties" value="${jboss.server.config.dir}/application-users.properties"/>
                  <module-option name="rolesProperties" value="${jboss.server.config.dir}/application-roles.properties"/>
                  <module-option name="realm" value="ApplicationRealm"/>
                  <module-option name="password-stacking" value="useFirstPass"/>
              </login-module>
          </authentication>
      </security-domain>
    Note:
    The JBoss AS 5 authentication documentation states that in the absence of a specified security-domain, the jboss-web-policy and ejb-ejb-policy will be used as a default. Notice they factory-provided defaults to not include authentication as a part of these two defaults. That is why your earlier EJB-RMI and Web-based accesses have been able to operate using anonymous access to the application so far.
                    <security-domain name="jboss-web-policy" cache-type="default">
                        <authorization>
                            <policy-module code="Delegating" flag="required"/>
                        </authorization>
                    </security-domain>
                    <security-domain name="jboss-ejb-policy" cache-type="default">
                        <authorization>
                            <policy-module code="Delegating" flag="required"/>
                        </authorization>
                    </security-domain>
    Note:
    The application-users.properties is used in two or more places. In addition to being used to authenticate a user accessing out application as addressed above, the server comes configured to use the same application-users.properties file to define authentication into the ApplicationRealm.
        <management>
            <security-realms>
                <security-realm name="ManagementRealm">
                    <authentication>
                        <properties path="mgmt-users.properties" relative-to="jboss.server.config.dir"/>
                    </authentication>
                </security-realm>
                <security-realm name="ApplicationRealm">
                    <authentication>
                        <properties path="application-users.properties" relative-to="jboss.server.config.dir"/>
                    </authentication>
                </security-realm>
            </security-realms>
            ...
        </management>

    That ApplicationRealm is the assigned security-realm of the remoting-connector which we use for the RMI communications we have used to date.

            <subsystem xmlns="urn:jboss:domain:remoting:1.1">
                <connector name="remoting-connector" socket-binding="remoting" security-realm="ApplicationRealm"/>
            </subsystem>

    That is why you needed to add a valid username/password to your jndi.properties file. This allowed you to authenticate access to the server prior to advancing to the individual application.

    $ cat javaeeextest/src/test/resources/jndi.properties
    ...
    java.naming.security.principal=${jboss.remoting.java.naming.security.principal}
    java.naming.security.credentials=${jboss.remoting.java.naming.security.credentials}
    ...
    $ cat javaeeextest/target/test-classes/jndi.properties
    ...
    java.naming.security.principal=known
    java.naming.security.credentials=password1!
    ...
  4. As an optional experiment with security-realm option -- comment out the principal and credentials properties within jndi.properties.
    $ cat javaeeextest/src/test/resources/jndi.properties
    ...
    #java.naming.security.principal=${jboss.remoting.java.naming.security.principal}
    #java.naming.security.credentials=${jboss.remoting.java.naming.security.credentials}
    ...
  5. Re-deploy and re-test your application. This will fail because of the assigned security-realm for the remoting-connector.
    $ mvn clean verify -rf :javaeeExTest
    
    Tests in error: 
      testPing(myorg.javaeeex.ejbclient.RegistrarIT): Failed to create remoting connection
      testCreatePerson(myorg.javaeeex.ejbclient.RegistrarIT): Failed to create remoting connection
      testLazy(myorg.javaeeex.ejbclient.RegistrarIT): Failed to create remoting connection
      testPOJO(myorg.javaeeex.ejbclient.RegistrarIT): Failed to create remoting connection
      testDTOs(myorg.javaeeex.ejbclient.RegistrarIT): Failed to create remoting connection
      testWebUseCase(myorg.javaeeex.ejbclient.RegistrarIT): Failed to create remoting connection
    
    Tests run: 6, Failures: 0, Errors: 6, Skipped: 0
    ...
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
    ./javaeeExTest/target/failsafe-reports/myorg.javaeeex.ejbclient.RegistrarIT.txt
    ::::::::::::::
     -------------------------------------------------------------------------------
    Test set: myorg.javaeeex.ejbclient.RegistrarIT
     -------------------------------------------------------------------------------
    Tests run: 6, Failures: 0, Errors: 6, Skipped: 0, Time elapsed: 0.934 sec <<< FAILURE!
    testPing(myorg.javaeeex.ejbclient.RegistrarIT)  Time elapsed: 0.472 sec  <<< ERROR!
    javax.naming.NamingException: Failed to create remoting connection [Root exception is java.lang.RuntimeException: javax.security.sasl.SaslException: Au
    thentication failed: all available authentication mechanisms failed]
            at org.jboss.naming.remote.client.ClientUtil.namingException(ClientUtil.java:36)
            at org.jboss.naming.remote.client.InitialContextFactory.getInitialContext(InitialContextFactory.java:121)
            at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
            at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:305)
            at javax.naming.InitialContext.init(InitialContext.java:240)
            at javax.naming.InitialContext.<init>(InitialContext.java:192)
            at myorg.javaeeex.ejbclient.RegistrarIT.setUp(RegistrarIT.java:39)
  6. Remove the security-realm attribute from the remoting-connector and re-run your tests. They should now pass again without the principal and credentials since neither the remoting-connector and your application require authentication at this time.
    $ cat standalone/configuration/standalone.xml
    ...
            <subsystem xmlns="urn:jboss:domain:remoting:1.1">
                <connector name="remoting-connector" socket-binding="remoting"/>
            </subsystem>
    ...
    $ mvn verify -rf :javaeeExTest
    
    ...
    Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
    ...
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
  7. Observe the unauthenticated identity your application is receiving without supplying credentials. Add a query of the caller ID within the first EJB invoked -- the TestUtilEJB.
    $ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/TestUtilEJB.java
    
    ...
    import javax.annotation.Resource;
    
    import javax.ejb.SessionContext;
    ...
    @Stateless
    public class TestUtilEJB implements TestUtilRemote {
        private static Log log = LogFactory.getLog(TestUtilEJB.class);
    
        @Resource
        private SessionContext ctx;
        
    ...
        public void resetAll() throws Exception {
            try {
                log.debug("caller=" + ctx.getCallerPrincipal().getName());
                testUtil.resetAll();
  8. Rebuild and redeploy your application. Verify that the server log indicates that the unauthenticated-principal value is being used.
    $ mvn clean install -rf :javaeeExEJB
    
    ...
    Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
    ...
    [INFO] Java EE Exercise EJB .............................. SUCCESS [6.169s]
    [INFO] Java EE Exercise WAR .............................. SUCCESS [2.553s]
    [INFO] Java EE Exercise EAR .............................. SUCCESS [1.302s]
    [INFO] Java EE Exercise Remote Test ...................... SUCCESS [19.988s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    //SERVER log
    20:21:25,024  *** TestUtilEJB:init() ***
    20:21:25,052 caller=anonymous
    20:21:25,061 found 4 statements
    20:21:25,062 executing:
        alter table JAVAEEEX_ADDRESS 
    ...
    Note:
    If your server.log output is missing the debug statement above, check that your standalone.xml file has the following set in the logging section.
                <logger category="myorg">
                    <level name="DEBUG"/>
                </logger>
  9. Put back the security-realm specification and uncomment the principal and credentials in the jndi.properties file. We will use the principal within the jndi.properties file as our default user that can access the application but without assigned roles to do anything.
    $ cat .../standalone/configuration/standalone.xml
    
    ...
            <subsystem xmlns="urn:jboss:domain:remoting:1.1">
                <connector name="remoting-connector" socket-binding="remoting" security-realm="ApplicationRealm"/>
            </subsystem>
    ...
    $ cat javaeeExTest/src/test/resources/jndi.properties
    ...
    java.naming.security.principal=${jboss.remoting.java.naming.security.principal}
    java.naming.security.credentials=${jboss.remoting.java.naming.security.credentials}
    ...
  10. Rebuild and re-deploy your application to make sure all is functional before moving on.
    $ mvn clean install
    
    ...
    [INFO] Java EE Exercise .................................. SUCCESS [0.669s]
    [INFO] Java EE Exercise Impl ............................. SUCCESS [15.644s]
    [INFO] Java EE Exercise EJB .............................. SUCCESS [4.093s]
    [INFO] Java EE Exercise WAR .............................. SUCCESS [2.876s]
    [INFO] Java EE Exercise EAR .............................. SUCCESS [2.317s]
    [INFO] Java EE Exercise Remote Test ...................... SUCCESS [15.629s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS

    At this point we are a bit more familiar with the security-domains within the application server and how our application has been interacting with these domains in a default way. We know there is an additional security-domain called "other" that can perform authentication based on username and password credentials and we verified that domain has the accounst we require to continue with the exercise. Now we need to do some things on our application and client sides.

Define Role Access Restrictions

Lets do some quick work to begin locking down the application. The resetAll() method can now be called by any, anonymous user by design. Lets change that design to require the caller to authenticate into the admin role. We don't yet have the ability to authenticate users into a role but we will start by defining which roles the EJBs and methods require.

  1. Define a RolesAllowed restriction for resetAll().
    $ cat ./javaeeExEJB/src/main/java/myorg/javaeeex/ejb/TestUtilEJB.java
    
    ...
    import javax.annotation.security.RolesAllowed;
    ...
    
        @RolesAllowed({"admin"})
        public void resetAll() throws Exception {
  2. Rebuild and re-test your application. It should still pass even though the anonymous user is not in the admin role.
    $ mvn clean verify -rf :javaeeExEJB -Dit.test=myorg.javaeeex.ejbclient.RegistrarIT#testCreatePerson
    ...
    //SERVER log
    20:31:55,006  *** TestUtilEJB:init() ***
    20:31:55,012 caller=anonymous
    20:31:55,014 found 4 statements
    20:31:55,014 executing:
        alter table JAVAEEEX_ADDRESS 
            drop constraint FKEB70B40A6E18CE38
    ...
  3. The above still passed because we have not activated security for the application yet. To activate security features, assign your EJB tier to the "other" security domain by adding the following jboss-ejb3.xml file to the META-INF directory.
    $ cat javaeeExEJB/src/main/resources/META-INF/jboss-ejb3.xml
    
    <?xml version="1.0"?>
    <jboss:ejb-jar
        xmlns:jboss="urn:jboss:domain:ejb3:1.2"
        xmlns:sec="urn:security"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd
            urn:security urn:security"
        version="3.1"
        impl-version="2.0">
        <assembly-descriptor>
            <sec:security>
                <ejb-name>*</ejb-name>
                <sec:security-domain>other</sec:security-domain>
            </sec:security>
        </assembly-descriptor>
    </jboss:ejb-jar>
  4. Rebuild and re-deploy your application. It should now fail because of an authorization (not authentication -- yet) failure.
    $ mvn clean verify -rf :javaeeExEJB -Dit.test=myorg.javaeeex.ejbclient.RegistrarIT#testPing
    
    ...
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.088 sec <<< FAILURE!
    
    Results :
    
    Tests in error: 
      testPing(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract void myorg.javaeeex.bl.TestUtil.resetAll() throws java.lang.Exception of bean: TestUtilEJB is not allowed
    
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
    
    ...
    [INFO] Java EE Exercise EJB .............................. SUCCESS [5.888s]
    [INFO] Java EE Exercise WAR .............................. SUCCESS [2.293s]
    [INFO] Java EE Exercise EAR .............................. SUCCESS [1.234s]
    [INFO] Java EE Exercise Remote Test ...................... FAILURE [10.527s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
    ./javaeeExTest/target/failsafe-reports/myorg.javaeeex.ejbclient.RegistrarIT.txt
    ::::::::::::::
     -------------------------------------------------------------------------------
    Test set: myorg.javaeeex.ejbclient.RegistrarIT
     -------------------------------------------------------------------------------
    Tests run: 6, Failures: 0, Errors: 6, Skipped: 0, Time elapsed: 1.803 sec <<< FAILURE!
    testPing(myorg.javaeeex.ejbclient.RegistrarIT)  Time elapsed: 1.107 sec  <<< ERROR!
    javax.ejb.EJBAccessException: JBAS014502: Invocation on method: public abstract void myorg.javaeeex.bl.TestUtil.resetAll() throws java.lang.Exception o
    f bean: TestUtilEJB is not allowed
            at org.jboss.as.ejb3.security.AuthorizationInterceptor.processInvocation(AuthorizationInterceptor.java:101)

    At this point we can now protect our methods from being accessed by unauthorized callers. Next we will provide a means to authenticate authorized users.

Implement Client Login

  1. Define the admin user credentials for the client to use. We can pass them thru the failsafe (not surefire -- this in an integration test) plugin as follows.
    $ cat javaeeExTest/pom.xml
    
    ...
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-failsafe-plugin</artifactId>
                    <configuration>
                        <argLine>${surefire.argLine}</argLine>
                        <systemPropertyVariables>
                            <admin.user>admin1</admin.user>
                            <admin.password>password1!</admin.password>
                        </systemPropertyVariables>
                    </configuration>
                </plugin>
    ...
  2. Read in the admin user credentials into the client program but also define a reasonable default within the JUnit test so that we can run the integration test within Eclipse -- outside of maven during development. Although maven can create a very robust testbed, you will want to the ability to do something by default right within Eclipse without maven.
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    
    public class RegistrarIT {
    ...
        private static final String adminUser = System.getProperty("admin.user", "admin1");
        private static final String adminPassword = System.getProperty("admin.password", "password1!");
    ...
    
        @Before
        public void setUp() throws Exception {
    ...
            log.debug(String.format("admin= %s/%s", adminUser, adminPassword));
    ...
        }
  3. Add a helper method that will perform login with JNDI and return a JNDI Context associated with the provided credentials. Note that the Context.SECURITY_PRINCIPAL and CREDENTIALS are the properties we worked with earlier in the jndi.properties file.
    import java.util.Properties;
    import javax.naming.NamingException;
    ...
    
        private Context runAs(String username, String password) throws NamingException {
            if (jndi!=null) {
                    jndi.close();
            }
            Properties env = new Properties();
            if (username != null) {
                env.put(Context.SECURITY_PRINCIPAL, username);
                env.put(Context.SECURITY_CREDENTIALS, password);
            }
            log.debug(String.format("%s env=%s", username==null?"anonymous":username, env));
            jndi=new InitialContext(env);
            return jndi;
        }
  4. Change the object-level jndi variable from an InitialContext to a Context type so that we do not run into type issues when using the runAs() helper method.
    import javax.naming.Context;
    ...
        private Context jndi;
  5. Replace all existing "new InitialContext()" calls with "runAs(null, null)".
            log.debug("getting jndi initial context");
            jndi = runAs(null, null);
            log.debug("jndi=" + jndi.getEnvironment());
            jndi.lookup("/"); //do a quick comms check of JNDI
  6. Add an @After method to close the InitialContext that may be open at the end of the test. I have found that if I do not do that -- the threaded aspect of JBoss remoting seems to get a little lost and erratic.
        /**
         * It is important to close the JNDI context in between tests
         */
        @After
        public void tearDown() throws NamingException {
            if (jndi != null) {
                    jndi.close();
                    jndi=null;
            }
        }
  7. Authenticate as admin1 just prior to calling resetAll() and then revert the identity back to the default before leaving the cleanup method.
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    
    ...
        protected void cleanup() throws Exception {
            log.info("calling testUtil.resetAll()");
            ((TestUtilRemote)runAs(adminUser, adminPassword).lookup(testUtilJNDI)).resetAll();
            log.info("testUtil.resetAll() complete");
        }
    ...
  8. If you rebuild and re-deploy your solution at this point it will continue to fail. We will work to figure out the problem and fix.
    $ mvn clean verify -rf :javaeeExEJB -Dit.test=myorg.javaeeex.ejbclient.RegistrarIT#testPing
    
    ...
    Tests in error: 
      testPing(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract void myorg.javaeeex.bl.TestUtil.resetAll() throws java.lang.Exception of bean: TestUtilEJB is not allowed
    
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0
    
    ...
    [INFO] Java EE Exercise EJB .............................. SUCCESS [5.868s]
    [INFO] Java EE Exercise WAR .............................. SUCCESS [2.353s]
    [INFO] Java EE Exercise EAR .............................. SUCCESS [1.426s]
    [INFO] Java EE Exercise Remote Test ...................... FAILURE [12.136s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
  9. Temporarily comment out the @RolesAllowed restriction on resetAll() to view the caller identity calling the EJB. Notice that the admin1 credential did not get passed or used by the server. We still have the principal from the jndi.properties being used.
    $ mvn clean verify -rf :javaeeExEJB -Dit.test=myorg.javaeeex.ejbclient.RegistrarIT#testPing
    ...
    //SERVER log
    12:21:44,265  *** TestUtilEJB:init() ***
    12:21:44,272 caller=known
    12:21:44,274 found 4 statements
  10. Perminently comment out the principal and credentials from the jndi.properties file.
    ...
    #java.naming.security.principal=${jboss.remoting.java.naming.security.principal}
    #java.naming.security.credentials=${jboss.remoting.java.naming.security.credentials}
    ...
  11. Add the known credentials to the pom failsafe configuration.
    $ cat javaeeExTest/pom.xml
    
    ...
                        <systemPropertyVariables>
                            <known.user>known</known.user>
                            <known.password>password1!</known.password>
                            <admin.user>admin1</admin.user>
                            <admin.password>password1!</admin.password>
                        </systemPropertyVariables>
    ...
  12. Add the assignment of the known credentials to java variables within the integration test. Go ahead and define a defaul value that should work in most development cases so that the integration test can be launched outside of maven.
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    ...
        private static final String knownUser = System.getProperty("known.user", "known");
        private static final String knownPassword = System.getProperty("known.password", "password1!");
    ...
  13. Create a second helper method called "runAs()" that will revert to the default/known user.
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    
    ...
        private Context runAs() throws NamingException {
            return runAs(knownUser, knownPassword);
        }
    ...
  14. Replace all calls to "runAs(null, null)" to "runAs()".
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    
    ...
            log.debug("getting jndi initial context");
            jndi = runAs();
            log.debug("jndi=" + jndi.getEnvironment());
            jndi.lookup("/"); //do a quick comms check of JNDI
    ...
        protected void cleanup() throws Exception {
            log.info("calling testUtil.resetAll()");
            ((TestUtilRemote)runAs(adminUser, adminPassword).lookup(testUtilJNDI)).resetAll();
            log.info("testUtil.resetAll() complete");
        }
  15. Rebuild and re-test your application. Notice that the TestUtilEJB should now report the caller is admin1.
    $ mvn clean verify -rf :javaeeExEJB -Dit.test=myorg.javaeeex.ejbclient.RegistrarIT#testPing
    
    ...
    //SERVER log
    12:44:54,229  *** TestUtilEJB:init() ***
    12:44:54,232 caller=admin1
    12:44:54,233 found 4 statements
  16. Re-add the @RolesAllowed restriction to TestUtilEJB.
    $ cat javaeeExEJB/src/main/java/myorg/javaeeex/ejb/TestUtilEJB.java
    
    ...
        @RolesAllowed({"admin"})
        public void resetAll() throws Exception {
  17. If you rebuild, re-deploy, and retest your application it should correctly authenticate and authorize the call to resetAll().
    $ mvn clean install -rf :javaeeExEJB -Dit.test=myorg.javaeeex.ejbclient.RegistrarIT#testPing
    
    ...
    [INFO] Java EE Exercise EJB .............................. SUCCESS [6.067s]
    [INFO] Java EE Exercise WAR .............................. SUCCESS [2.389s]
    [INFO] Java EE Exercise EAR .............................. SUCCESS [1.692s]
    [INFO] Java EE Exercise Remote Test ...................... SUCCESS [12.944s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS
    Note:
    After each change to the EJB I am changing the command from an "verify -rf :javaeeExTest" or "install -rf :javaExEJB" to go all the way back and rebuild the EJB and EAR as well as to place their implementation in the local repository for future commands where I simply issue a "verify -rf :javaeeExTest" and deploy the previous EAR and EJB to the server. If you are not careful to install the latest EJB/EAR change into the repository you could be working with an older implementation.
  18. Temporarily munge the admin password to verify our security setup is working.
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    
    ...
        protected void cleanup() throws Exception {
            ...
            runAs(adminUser, "badpass");
            ...
        }
    ...
  19. Observe that in the details of the error message it states that we failed to authenticate. Great! that's what we wanted.
    $ mvn clean verify -rf :javaeeExEJB -Dit.test=myorg.javaeeex.ejbclient.RegistrarIT#testPing
    
    ...
     -admin1 env={java.naming.security.principal=admin1, java.naming.security.credentials=badpass}
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.06 sec <<< FAILURE!
    
    Results :
    
    Tests in error: 
      testPing(myorg.javaeeex.ejbclient.RegistrarIT): Failed to create remoting connection
    
    ...
    [INFO] BUILD FAILURE
    ./javaeeExTest/target/failsafe-reports/myorg.javaeeex.ejbclient.RegistrarIT.txt
    ::::::::::::::
     -------------------------------------------------------------------------------
    Test set: myorg.javaeeex.ejbclient.RegistrarIT
     -------------------------------------------------------------------------------
    Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 1.06 sec <<< FAILURE!
    testPing(myorg.javaeeex.ejbclient.RegistrarIT)  Time elapsed: 0.771 sec  <<< ERROR!
    javax.naming.NamingException: Failed to create remoting connection [Root exception is java.lang.RuntimeException: javax.security.sasl.SaslException: Au
    thentication failed: all available authentication mechanisms failed]
            at org.jboss.naming.remote.client.ClientUtil.namingException(ClientUtil.java:36)
            at org.jboss.naming.remote.client.InitialContextFactory.getInitialContext(InitialContextFactory.java:121)
            at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:684)
            at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:305)
            at javax.naming.InitialContext.init(InitialContext.java:240)
            at javax.naming.InitialContext.<init>(InitialContext.java:214)
            at myorg.javaeeex.ejbclient.RegistrarIT.runAs(RegistrarIT.java:82)
            at myorg.javaeeex.ejbclient.RegistrarIT.cleanup(RegistrarIT.java:66)
  20. Un-munge the admin password for the cleanup method.
        protected void cleanup() throws Exception {
            ...
            runAs(adminUser, adminPassword);
            ...
        }
  21. Rebuild and re-deploy the application. If everything is setup correctly
    • the credentials should be passed into the JVM from the failsafe configuration
    • the test setup should be taking the credentials and using them to authenticate with the server
    • the cleanup() method is logging in as the admin
    • the credentials provided by the client match the credentials registered on the server an do not reject the user as an unknown user.
    • the authenticated user roles match the restrictions defined for the EJB method and allow the resetAll() to be invoked.
      $ mvn clean install
      
      ...
      Tests run: 6, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 6.505 sec
      
      Results :
      
      Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
      
      [WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
      [INFO] 
      [INFO] --- cargo-maven2-plugin:1.2.3:undeploy (cargo-post) @ javaeeExTest ---
      [INFO] 
      [INFO] --- maven-failsafe-plugin:2.12.2:verify (verify) @ javaeeExTest ---
      ...
      [INFO] Java EE Exercise .................................. SUCCESS [0.761s]
      [INFO] Java EE Exercise Impl ............................. SUCCESS [17.815s]
      [INFO] Java EE Exercise EJB .............................. SUCCESS [4.466s]
      [INFO] Java EE Exercise WAR .............................. SUCCESS [3.088s]
      [INFO] Java EE Exercise EAR .............................. SUCCESS [1.159s]
      [INFO] Java EE Exercise Remote Test ...................... SUCCESS [16.695s]
      [INFO] ------------------------------------------------------------------------
      [INFO] BUILD SUCCESS

Define Remaining Access Restrictions

Once you completed the previous steps you have all the mechanics down for adding authentication, access control, and authorization to your server and application. In this section we will just add breadth to the overall implementation.

  1. Add a default restriction for all methods in the RegistrarEJB by defining a RolesAllowed at the class level.
    $ cat javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarEJB.java
    
    ...
    import javax.annotation.security.RolesAllowed;
    ...
    @Stateless
    @RolesAllowed({"user"})
    public class RegistrarEJB implements RegistrarLocal, RegistrarRemote {
  2. Rebuild and redeploy the application to verify the new restrictions are in place. At this point all business methods in the RegistrarEJB should indicate we have authorization issues.
    $ mvn install -rf :javaeeExEJB
    
    Tests run: 6, Failures: 0, Errors: 6, Skipped: 0, Time elapsed: 2.959 sec <<< FAILURE!
    
    Results :
    
    Tests in error: 
      testPing(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract void myorg.javaeeex.ejb.RegistrarRemote.ping() of bean: RegistrarEJB is not allowed
    
    ...
    Tests run: 6, Failures: 0, Errors: 6, Skipped: 0
    
    ...
    [INFO] Java EE Exercise EJB .............................. SUCCESS [6.078s]
    [INFO] Java EE Exercise WAR .............................. SUCCESS [2.523s]
    [INFO] Java EE Exercise EAR .............................. SUCCESS [2.165s]
    [INFO] Java EE Exercise Remote Test ...................... FAILURE [14.380s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD FAILURE
  3. Allow anyone to invoke ping() by assigning PermitAll specifically to the method. This will override the default defined at the class level.
    $ cat javaeeExEJB/src/main/java/myorg/javaeeex/ejb/RegistrarEJB.java
    
    ...
    import javax.annotation.security.PermitAll;
    import javax.annotation.Resource;
    import javax.ejb.SessionContext;
    ...
        @Resource
        protected SessionContext ctx;
    ...
        @PermitAll
        public void ping() {
            log.debug("ping called");
            log.debug("caller=" + ctx.getCallerPrincipal().getName());
        }
  4. Rebuild and redeploy the application to verify the new restrictions are in place. At this point the ping() method should allow anonymous clients to invoke it so we should have one less failure.
    $ mvn install -rf :javaeeExEJB
    
    ...
    Tests run: 6, Failures: 0, Errors: 5, Skipped: 0, Time elapsed: 3.216 sec <<< FAILURE!
    
    Results :
    
    Tests in error: 
      testCreatePerson(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract myorg.javaeeex.bo.Person myorg.javaeeex.ejb.RegistrarRemote.createPerson(myorg.javaeeex.bo.Person) throws myorg.javaeeex.bl.RegistrarException of bean: RegistrarEJB is not allowed
      testLazy(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract myorg.javaeeex.bo.Person myorg.javaeeex.ejb.RegistrarRemote.createPerson(myorg.javaeeex.bo.Person) throws myorg.javaeeex.bl.RegistrarException of bean: RegistrarEJB is not allowed
      testPOJO(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract myorg.javaeeex.bo.Person myorg.javaeeex.ejb.RegistrarRemote.createPerson(myorg.javaeeex.bo.Person) throws myorg.javaeeex.bl.RegistrarException of bean: RegistrarEJB is not allowed
      testDTOs(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract myorg.javaeeex.bo.Person myorg.javaeeex.ejb.RegistrarRemote.createPerson(myorg.javaeeex.bo.Person) throws myorg.javaeeex.bl.RegistrarException of bean: RegistrarEJB is not allowed
      testWebUseCase(myorg.javaeeex.ejbclient.RegistrarIT): JBAS014502: Invocation on method: public abstract myorg.javaeeex.bo.Person myorg.javaeeex.ejb.RegistrarRemote.createPerson(myorg.javaeeex.bo.Person) throws myorg.javaeeex.bl.RegistrarException of bean: RegistrarEJB is not allowed
    
    Tests run: 6, Failures: 0, Errors: 5, Skipped: 0
    //SERVER LOG
    19:16:32,007 **** init ****
    19:16:32,007 init complete, registrar=myorg.javaeeex.blimpl.RegistrarImpl@15e6726
    19:16:32,008 ping called
    19:16:32,008 caller=known
  5. Define user credentials in the RMI Test
    $ cat javaeeExTest/pom.xml
    ...
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-failsafe-plugin</artifactId>
    ...
                            <user.user>user1</user.user>
                            <user.password>password1!</user.password>
    ...  
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    
    ...
        private static final String userUser = System.getProperty("user.user","user1");
        private static final String userPassword = System.getProperty("user.password", "password1!");
    ...
  6. Change the "runAs()" at the end of the @Before method to use the user1 account. This will be the default user for each test and then the specific tests can perform surgical logout/login around certain functionality.
    $ cat javaeeExTest/src/test/java/myorg/javaeeex/ejbclient/RegistrarIT.java
    
    ...
        @Before
        public void setUp() throws Exception {
            ...
            cleanup();
            //run the tests as user
            Context context=runAs(userUser, userPassword);
            registrar=(RegistrarRemote)context.lookup(registrarJNDI);
        }
    ...
  7. Rebuild and redeploy the application to verify the new restrictions are in place. At this point the user should be able to complete all actions within the RMI Test.
    $ mvn install -rf :javaeeExEJB
    
    ...
    Tests run: 6, Failures: 0, Errors: 0, Skipped: 0
    
    ...
    [INFO] Java EE Exercise EJB .............................. SUCCESS [6.313s]
    [INFO] Java EE Exercise WAR .............................. SUCCESS [2.219s]
    [INFO] Java EE Exercise EAR .............................. SUCCESS [1.766s]
    [INFO] Java EE Exercise Remote Test ...................... SUCCESS [18.469s]
    [INFO] ------------------------------------------------------------------------
    [INFO] BUILD SUCCESS

    At this point you can experiment with the remaining methods and user identities. For example, it may be interesting to use a user with multiple roles to verify they work across roles as well as users with no roles to verify a user with a valid login will be rejected if they do not have the right roles.

Summary

In this exercise we secured an EJB according to an application policy and enabled access to authorized users. More specifically we ...

  • Defined an application policy that determined how the application server should authenticate users and determine roles assigned to users
  • Associated an EJB application with a specific application policy
  • Defined roles required to access an EJB method or the entire EJB
  • Defined the users, user credentials, and role assignment to the application policy
  • Defined a login configuration for the remote client.
  • Established login sessions within the remote client.

Next steps should be to extend the security implementation to the web tier because at this point the web tier will not longer be able to access the EJB's role-constrained methods.

The following is an overview of the primary modules accessed during this exercise.

|-- javaeeExImpl
|-- javaeeExEJB
|   |-- pom.xml
|   `-- src
|       `-- main
|           |-- java
|           |   `-- myorg
|           |       `-- javaeeex
|           |           |-- cdi
|           |           |   `-- ResourceConfig.java
|           |           |-- dto
|           |           |   |-- AddressDTO.java
|           |           |   `-- PersonDTO.java
|           |           `-- ejb
|           |               |-- RegistrarEJB.java
|           |               |-- RegistrarLocal.java
|           |               |-- RegistrarRemote.java
|           |               |-- TestUtilEJB.java
|           |               `-- TestUtilRemote.java
|           `-- resources
|               `-- META-INF
|                   |-- beans.xml
|                   |-- jboss-ejb3.xml
|                   `-- persistence.xml
|-- javaeeExWAR
|-- javaeeExEAR
|-- javaeeExTest
|   |-- pom.xml
|   `-- src
|       `-- test
|           |-- java
|           |   `-- myorg
|           |       `-- javaeeex
|           |           `-- ejbclient
|           |               `-- RegistrarIT.java
|           `-- resources
|               |-- jndi.properties
|               `-- log4j.xml
`-- pom.xml