Secure a Web application, Java-style

Use Java's multiple-layer security implementation to protect your Web

1 2 Page 2
Page 2 of 2

Real-world example

The preceding examples show how a form-based authentication scheme can work, how the EJB model can restrict access based on a username/password combination, and how you can use the access control lists within the Java security model to restrict access to functionality. I now intend to leverage these examples to demonstrate an overall system approach to the security problem.

In the first example, the form-based authentication scheme was implemented in a rudimentary fashion. It checked only to ensure that the user was contained in the database of authenticated users. A user contained in the database was granted access to all functionality within the system without further definition.

In the second example, the EJB authorized the user attempting to execute restricted methods on the bean. This security protects the bean from unauthorized access, but does not protect the Web application.

In the third example, I discussed the Java Security Access Control package. I demonstrated how you could use a simple API to verify that a user had access to certain functionality within the system.

By gathering these three examples, you can create a simple authentication scheme that limits the user's access to Web-based components of a system, including back-office systems.

The goal of this next example is to demonstrate the collective power of these three security mechanisms and to provide a pattern by which you can build more advanced security frameworks. All code used in this example can be found in Resources.

Delegate security to the Java Access Control Model

The first step in this example is to create delegate classes to wrap the security functionality contained in the Java Access Control Model classes. By wrapping the method calls and interfaces, the developer can ensure that the majority of the code in the system can function independently of the security implementation. In addition, through the Delegation pattern, the remainder of the code can perform security functionality without obtaining specific knowledge of the inner workings of Sun's security model.

The first major component of this example is the User. The code that implements the interface can delegate calls to the java.security.Principal interface. In addition to the methods contained in the Principal interface, the User interface will blueprint other methods for obtaining or modifying information related to a given system user.

For example, to retrieve a user's telephone number, you could implement a method called getPhoneNumber(). Another approach to obtaining this user data involves the use of XML. You could convert data stored in the database into an XMLDocument from which data could be accessed by walking the tree. This example does not delve into either approach in great detail, leaving it to the reader as an exercise.

The second major component I'll discuss is the Feature. The classes that implement this interface use the implementation of the java.security.acl.Permission interface to perform their functionality. In a Web-based system, there is a need to identify both the name of the action (for example, "run report") and the URL related to that action.

Although this is possible through the Java security model, you have to store both attributes in a single String and then parse for the individual attributes. While this works, it is dependent on the formatting, which can vary at runtime, rather than on a compiled interface as has been developed here.

The last major component is the WebSecurityManager object. This object is responsible for performing the duties related to user management, features management, and the access control lists that establish the relationships between users and features.

You can implement the WebSecurityManager in many ways including, but not limited to, a Java bean used by JSP, a servlet, an EJB, or a CORBA/RMI service. The choice is up to the system's designer who must satisfy customer requirements. In this simple example, the WebSecurityManager is assumed to run in the same JVM as the servlets/JSP.

Load users

In the last section, I introduced the important objects of this scheme. In this section, I'll further develop the interactions of these objects.

For the sake of this example, let's make two assumptions: first, the information relating the users and their permissions is stored in a relational database as opposed to an LDAP or an external object repository; second, this database is already populated. By making the second assumption, I am limiting the scope of this article to the retrieval of users from the database; the process of storing users is performed identically but in the opposite direction.

This example builds off of the framework detailed in the prior example of servlet-based user authentication. The service method of the BaseServlet should now be expanded to include the following:

public void service(HttpServletRequest request, HttpServletResponse response)
      throws IOException, ServletException
   {
      // check to see if a session has already been created for this user
      // don't create a new session yet.
      HttpSession session = request.getSession( false);
      String sRequestedFeature = request.getParameter(Constants.FEATURE);
      if ( session != null)
      {
         // retrieve the User object from the session
         User currentUser = (User) session.getValue(Constants.USER);
         Feature featureRequested = null;
         try {
            // get the page from the security manager
            featureRequested = WebSecurityManager.getFeature(
sRequestedFeature);
         } catch ( WebSecurityManagerException smE)
         {
            smE.printStackTrace();
         }
         if ( WebSecurityManager.isUserAuthenticated( currentUser,
featureRequested) )
         {
            // get page from feature
            String sRequestedPage = featureRequested.getFeaturePath();
            // redirect to the requested page
            response.sendRedirect( Constants.LOGIN2 + sRequestedPage);
         } else {
            // redirect to the error page
            response.sendRedirect( Constants.ERROR + sRequestedFeature);
         }
      } else {
         // redirect to the login servlet (passing parameter)
         response.sendRedirect( Constants.LOGIN2 + sRequestedFeature);
      }
   }

In this code snippet from the BaseServlet2 class, the user is authenticated against the access control list using the requested feature name. The user object is retrieved from the session. The feature object corresponding to the request parameter is retrieved from the SecurityManager object. The SecurityManager then checks the feature against the access control list that was created on the user login through the implementation of the access control list interface.

The SecurityManager functionality is dependent on the underlying database structures and the method by which the data is retrieved from the data source and packed into live objects in the JVM.

In this example, the SecurityManager is implemented as a simple class residing within the JVM. In another full-blown system, this may be implemented as a series of entity beans. For now, the SecurityManager will load all the features in the database on initialization into a Hashtable.

Upon login, the username/password combination is compared to the data stored in the database. If successful, the User object will be created and stored to the session. The features related to the user in the database are created and added to an access control list entry for the user. This entry is then added to the master access control list for the application. From then on, the application can delegate the responsibility of securing the application to the Java Access Control Model classes.

Here's a code snippet showing how the features are added to the access control list for a given user.

   private static void addAclEntry(User user, Hashtable hFeatures)
      throws WebSecurityManagerException
   {
      // create a new ACL entry for this user
      AclEntry newAclEntry = new AclEntryImpl(  user);
      // initialize some temporary variables
      String sFeatureCode = null;
      Feature feature = null;
      Enumeration enumKeys = hFeatures.keys();
      String keyName = null;
      while ( enumKeys.hasMoreElements() )
      {
         // Get the key name from the enumeration
         keyName = (String) enumKeys.nextElement();
         // retrieve the feature from the hashtable
         feature = (Feature) hFeatures.get(keyName);
         // add the permission to the aclEntry
         newAclEntry.addPermission( feature );
      }
      try {
         // add the aclEntry to the ACL for the _securityOwner
         _aclExample.addEntry(_securityOwner, newAclEntry);
      } catch (NotOwnerException noE)
      {
         throw new WebSecurityManagerException("In addAclEntry", noE);
      }
   }

The addAclEntry method is passed a User object and an array of Feature objects. Using these objects, it creates an AclEntry and then adds it to the Acl used by the application. It is precisely this Acl that is used by the BaseServlet2 to authenticate the user to the system.

Authorize the user for back-office integration

At this point, you have successfully authenticated the user to the Web system. Assuming that most business applications on the Web will want to integrate with back-office systems, you can consider a further security step if you desire.

Earlier in the article, I discussed the EJB security model. In that discussion, it was shown that each bean has the ability to have a username/password combination assigned to methods that it contains. You can use this to restrict method use in beans to certain Web applications. In other words, the extranet application can access methods related to the supply chain but not methods related to internal pay scales.

How does this apply to this example? You can create a further security check to ensure that Web users are mapped to either individual back-office users or user categories (for example, basic to advanced) based on their usernames. One way you could do this would be to maintain a mapping from the current user to that user's (or that user group's) back-office login information. The system could retrieve that mapping and populate the User object with this username/password combination. Using this technique, you could protect the back-office systems against a failure in the protection of a given screen. For example, if a user were able to access an administration page due to a typo or error in the code, he or she would fail to authorize against the EJB that dealt with the management of the other system users.

These additional authorization steps are left to the reader for investigation.

Conclusion

Securing a Web system is a major requirement for the development team. This article has put forth a security scheme that leverages the code developed by Sun Microsystems to secure objects in Java. Although this simple approach uses an access control list to regulate user access to protected features, you can expand it based on the requirements of your user community to support additional feature-level variations or user information.

Additional enhancements could include XML and would include the migration of code from a simple object making SQL calls to a bean, possibly even an entity bean.

Much of the discussion put forth in this article stems from the J2EE specification. Currently the containers do not provide a security mechanism as the one presented here. However, as the containers continue to grow, the implementation of both basic and form-based authentication may become present. In addition, a J2EE server without much customization may support the EJB mapping that was described earlier in the article. Java also provides some other additional methods of security ranging from digital signatures to the JAAS specification that can be used to protect the class files against unauthorized access.

A simple security approach can minimize your system development time, and vulnerability to malicious attack, while allowing for expanded features with minimal coding effort. As many companies just relearned, however, software alone cannot secure a Website against all forms of attacks.

Michael Cymerman is a consultant specializing in the development of Java/Internet software solutions. He provides Java-based architecture, design, and development solutions to Fortune 500 companies.

Learn more about this topic

1 2 Page 2
Page 2 of 2