Secure a Web application, Java-style

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

Web security can be defined in various ways, depending on individual points of view. The main focus of security in this article is the safety of applications developed and deployed for the Internet. Here, I will outline some software security measures that you can take to secure your application. While none of them is completely infallible, combining these approaches with hardware can help prevent malicious attacks on your business.

The two main concepts of security are authentication and authorization. I will describe each of them in the following sections and provide examples of how you can implement them in your applications. In addition, I will discuss some key classes of the Java Security API to prepare for a more detailed example that combines form-based authentication with Java's security model. The concepts outlined in the example should enable your enterprise to produce a security policy for your Java-based applications.

Authentication

Authentication is the process by which users' access privileges are verified prior to their entering a Website's protected area. There are two major authentication approaches: basic authentication and form-based authentication.

Basic authentication

Basic authentication relies on the Web server for authentication to protected areas. Sites protected by basic authentication let the user browse through unprotected areas without requiring the user to enter a password. However, the browser will automatically prompt the user for a password and username should he or she attempt to access a secure page. This prompt comes in the form of a dialog box.

The username and password combination is then encoded (base 64) and passed in an unencrypted form to the Web server. The Web server compares the encoded value against values stored in a flat file, a database, or a directory server.

If the user is authenticated, the server then verifies that the user has the privilege to access the requested page against a file, such as httpd.conf. If the user has access, the Web server then serves the page. If the user is denied access, the server either requests the username/password combination again or presents an error message on the browser window.

Because the actual syntax of the basic authentication varies between servers, I do not present any here. There are numerous Web resources describing the syntax of the various servers.

Form-based authentication

The majority of sites use an approach called form-based lazy authentication, which lets users navigate through unprotected areas of the site without requiring a password. Only when the user wants to access protected areas, such as ordering or account status, does the site present a login form. This is the most common security approach and is used by large e-commerce firms, such as Barnes & Noble. The benefit of this approach is that users are not subjected to the wait times associated with authentication unless they truly need access to a protected page.

As the most common security scheme on the Web, form-based lazy authentication lends itself to the example presented in this article.

Use forms to authenticate clients

A common way for servlet-based systems to perform authentication is to use the session to store information indicating that a user has logged into the system. In this scheme, the authentication logic uses the HttpSession object maintained by the servlet engine in the Web server.

A base servlet with knowledge of authentication is helpful in this case. Using the service method of the BaseServlet, the extending servlets can reuse the security check functionality. All code used in this example can be found in Resources.

The service method is shown in the following code snippet:

   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 requestedPage = request.getParameter(Constants.REQUEST);
      if ( session != null)
      {
         // retrieve authentication parameter from the session
         Boolean isAuthenticated = (Boolean)
session.getValue(Constants.AUTHENTICATION);
         // if the user is not authenticated
         if ( !isAuthenticated.booleanValue() )
         {
            // process the unauthenticated request
            unauthenticatedUser(response, requestedPage);
         }
      }
      else // the session does not exist
      {
         // therefore the user is not authenticated
         // process the unauthenticated request
         unauthenticatedUser(response, requestedPage);
        }
   }

Notice that you can expand this method to perform other generic functions as well. In this example, I developed only the security aspects of this class.

The BaseServlet attempts to retrieve the session from the servlet engine. On retrieval, the servlet verifies that the user has been granted access to the system. Should either of these checks fail, the servlet redirects the browser to the login screen.

On the login screen, the user is prompted for a username and password. Note that the data passed from the browser to the Web server is unencrypted unless you use Secure Socket Layer (SSL).

The LoginServlet uses the username/password combination to query the database to ensure that this user does indeed have access to the system. If the check fails to return a record for that user, the login screen is redisplayed. If the check is successful, the following code stores the user authentication information inside a session variable.

   // create a session
   session = request.getSession( true);
   // convert the boolean to a Boolean
   Boolean booleanIsAuthenticated = new Boolean( isAuthenticated);
   // store the boolean value to the session
   session.putValue(
      Constants.AUTHENTICATION,
      booleanIsAuthenticated);

In this example, it's assumed that any user who successfully authenticates to the system has access to the pages displayed prior to login. However, there are cases in which the application development team may require a more refined security approach to satisfy its requirements.

Authorization

Authorization verifies that the security policies protect against more sophisticated hackers by preventing unauthorized code from connecting to back-office systems, such as Enterprise JavaBeans (EJB). There are two types of authorization: code authorization and caller authorization.

Code authorization

Your security team can prevent unauthorized code use by limiting the classes available to the virtual machine used by the servlet engine. You can achieve this process of code authorization by removing unnecessary entries from the classpath.

The security team has complete control over which code should be included in the classpath and can therefore authorize the code prior to inclusion. The team should ensure that the code does not have access to third-party tools or extraneous code.

Caller authorization

In addition to code authorization, you can also authenticate the caller of back-office systems. The EJB model, for example, lets the development team specify a username and password for access to any bean deployed in a container.

Any code that attempts to connect to these beans must pass the username/password combination to the container for authorization. A failed authorization results in an exception that prevents the caller from completing its action.

Authorization example

In this second example, I'll explore the mechanisms built into the EJB specification for preventing unauthorized access to the business logic. While this example is based on the WebLogic Tengah server, the concepts hold true for other servers even though the syntax will differ. See Resources for the code or configuration files used in this example.

In the EJB's deployment descriptor, the following code identifies the access control entries associated with the bean:

(accessControlEntries
DEFAULT [administrators basicUsers]
theRestrictedMethod [administrators]
); end accessControlEntries

Notice that two user categories have been identified. Administrators have access to the bean by default and constitute the only user group that has access to theRestrictedMethod.

Once you've authorized that the administrators have access to the bean, you now need to create properties detailing which users are in the administrators group. To do this, the weblogic.properties file must include the following lines:

weblogic.password.JoeAdministrator=joe
weblogic.security.group.administrators=JoeAdministrator
weblogic.password.JaneBasic=jane
weblogic.security.group.basicUsers=JaneBasic

At this point, you've established the users who have access to the bean and have restricted certain specific methods. This helps limit the potential for malicious attacks on your Web server to reach the business logic stored in the beans.

The last step in this EJB authorization example is to establish the client connection to the bean. In this case, the client must specify the username/password combination properly in order to have access to the restricted bean or methods. An example client communication follows:

try{
Properties myProperties = new Properties();
myProperties.put( Context.INITIAL_CONTEXT_FACTORY,
"weblogic.jndi.T3InitialContextFactory");
myProperties.put(Context.PROVIDER_URL, "t3://localhost:7001");
myProperties.put(Context.SECURITY_PRINCIPAL, "JoeAdministrator");
myProperties .put(Context.SECURITY_CREDENTIALS, "joe");
ic = new InitialContext(myProperties);
...
}
catch (Exception e) { ... }

Since you've passed the JoeAdministrator user to the InitialContext, you'll have access to any method to which the administrators group has been granted access. If your application makes connections to external beans or your beans are used by external applications, you should implement this security authorization.

Java security

In the Java Security API, there is a package, java.security.acl, that contains several classes that you can use to establish a security system in Java. These classes enable your development team to specify different access capabilities for users and user groups. All code used in this example can be found in Resources.

The concept is fairly straightforward. A user or user group is granted permission to functionality by adding that user or group to an access control list. For example, consider a java.security.Principal called testUser as shown below:

Principal testUser = new PrincipalImpl ("testUser");

Now you can create a Permission object to represent the capability of reading from a file.

Permission fileRead = new PermissionImpl ("readFile");

Once you have created the user and the user's permission, you can create the access control list entry. It's important to note that the security APIs require that the owner of the access list be passed in order to ensure that this is truly the developer's desired action. It is essential that this owner object be protected carefully.

Acl accessList = new AclImpl (owner, "exampleAcl");

In its final form, the access list will contain a bunch of access list entries. You can create these as follows:

AclEntry aclEntry = new AclEntryImpl (testUser);
aclEntry.addPermission(fileRead);
accessList.addEntry(owner, aclEntry);

The preceding lines create a new AclEntry object for the testUser, add the fileRead permission to that entry, and then add the entry to the access control list.

You can now check the user permissions quite easily, as follows:

boolean isReadFileAuthorized = accessList.checkPermission(testUser,
readFile);

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

Join the discussion
Be the first to comment on this article. Our Commenting Policies