Sponsored by the Apache Software Foundation, Struts is an open source framework developed by the Jakarta Project. As stated on its homepage, Struts encourages architectures based on the Model-View-Controller (MVC), or Model 2, design paradigm. As many Web application developers have discovered, part of Struts's power lies in how easily developers can add extensions to its controller API classes as well as the tag libraries that comprise its view capabilities.

In "Mix Protocols Transparently in Web Applications" (JavaWorld, February 2002), I proposed extending Struts to incorporate a solution for mixing the HTTP and HTTPS protocols in Web applications. In this article, I walk through the steps necessary to extend Struts in that way and leverage the framework to add even more features to our solution.

Review

In my previous article, I discussed how many Web applications today transmit sensitive data, such as financial information, between the browser and Web server, in both directions. These applications usually employ the Secure Sockets Layer (SSL) and its companion protocol, HTTP over SSL (HTTPS), to prevent anyone monitoring Web transmission from viewing application data. Developing, deploying, and maintaining an application with mixed protocols can prove difficult, so I developed a solution utilizing J2EE's (Java 2 Platform, Enterprise Edition) redirect mechanism for MVC Web applications using servlets and JSPs (JavaServer Pages). The solution's full algorithm completes the following:

  • It determines the protocol and port number used to request the Web resource
  • If that protocol matches the protocol and port number we want for this resource, it does nothing
  • Otherwise, it checks for a query string
  • If no query string exists, it checks for request body parameters
  • If request body parameters exist, it converts these parameters to a query string
  • If any request attributes exist, it temporarily moves them to the session in a Map object
  • Finally, the algorithm performs a redirect to the same URL, using the correct protocol, modifying the specified port number, if necessary, and adding any existing or generated query string

Near the end of my previous article, I demonstrated the same mechanism for a Struts application and suggested extending Struts to incorporate the solution.

The Struts framework

Before we begin extending Struts, we must first understand how Struts works (for this article, we use the 1.0.1 release). In a typical Struts MVC application, a request made to a Struts action (in the controller role) might invoke some database or business operation and then forward the resulting JavaBean data objects (the model) to a JSP (the view) for display. The relationships between the classes that comprise the actions and the JSPs are defined at deployment time in an XML configuration file typically named struts-config.xml.

To better see how this happens, let's look closer at the Struts classes and files involved. The ActionServlet class defined in the org.apache.struts.action package provides the power behind the Struts framework. By processing all Struts requests and then calling the proper Struts action, this servlet serves as a Struts application's main controller. The servlet entry in our servlet's web.xml deployment descriptor looks like this:

  <servlet id="Servlet_1">
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <!-- Struts Config -->
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <!- other initialization parameters -->
    <load-on-startup>1</load-on-startup>
  </servlet>

The config initialization parameter defines the location of the Struts definition file, struts-config.xml.

To correctly distinguish Struts action requests, we create a servlet-mapping entry in the deployment descriptor, indicating that any URL request matching the pattern /do/* should forward to ActionServlet. That entry looks like this:

  <servlet-mapping>
    <servlet-name>action</servlet-name>
    <url-pattern>/do/*</url-pattern>
  </servlet-mapping>

For example, the ActionServlet will process a request to the URL /do/myAction, whereas it won't process a request to /doMyAction.

A typical action definition entry in the struts-config.xml file resembles this:

    <action path="/myAction" type="test.ssl.MyAction" >
      <forward name="success" path="/home.jsp" />
      <forward name="failure" path="/error.jsp" />
    </action>

This entry specifies that the test.ssl.MyAction class will execute the action named myAction. In addition to whatever request-processing logic MyAction contains, it also includes logic to determine which Web resource named in the forward tags to forward the request to. The MyAction class, or any class defined as an action, must extend the org.apache.struts.action.Action class. The Action class includes a method named perform() for processing HTTP (or HTTPS) requests. When extending Action to define a Struts action, you should override perform() to define logic similar to what you might define in a servlet's doGet() or doPost() methods. ActionServlet calls this perform() method when it processes a Struts action request.

When a Struts Web application deploys, the ActionServlet reads the struts-config.xml file and creates a mapping for each action defined therein for its specified class type and forward page elements. Each mapping is stored within an ActionMapping class instance, also found in the org.apache.struts.action package. ActionMapping contains a property for every attribute or child element of the action file element. All ActionMapping instances are then stored in an ActionMappings class instance. Figure 1 displays the diagram for these four classes.

Figure 1. UML diagram of the Struts 1.0 action-processing classes

Putting it all together, we see that when the ActionServlet receives a request, it searches its associated ActionMappings object to find the specific ActionMapping instance with the same name as the Struts action specified in the request. The ActionServlet then creates an instance of the Action subclass mapped to that action (if an instance does not already exist) and calls the perform() method on that instance.

For good measure, Struts also includes numerous custom tags to aid in JSP development. These tags are grouped into four libraries: bean, html, logic, and template.

Extend Struts

To incorporate our protocol-switching mechanism as a Struts extension, we first need a way to specify which action requests should transmit securely via HTTPS and which should transmit using HTTP. We specify that information in the struts-config.xml file.

Adding an attribute to the action tag element for specifying this secure/nonsecure choice appears tempting. However, that would require us to alter that file's document type definition (DTD), an action we should avoid. Fortunately, Struts provides a mechanism for easily adding properties to an action mapping: the set-property tag element. This subelement of the action tag element allows us to specify a property name and value for our action definition. After adding this element, our action definition now looks like this:

    <action path="/myAction" type="test.ssl.MyAction" >
      <set-property property="secure" value="true"/>
      <forward name="success" path="/home.jsp" />
      <forward name="failure" path="/error.jsp" />
    </action>

We will use the secure property as a flag where a value of true specifies transmission via HTTPS and false specifies HTTP. In our mixed protocol solution for this case, if ActionServlet receives a request for myAction via HTTP, ActionServlet will redirect that request back to myAction via HTTPS.

To use this new property, we define our own extension to the ActionMapping class named SecureActionMapping, which simply specifies the secure property addition. Here is the class definition:

package org.apache.struts.action;
public class SecureActionMapping extends ActionMapping {
    protected boolean secure;
    public void setSecure(boolean b){
        this.secure = b;
    }
    public boolean getSecure(){
        return this.secure;
    }
}

To actually use SecureActionMapping, we must tell the ActionServlet to use that class instead of ActionMapping. Struts again provides a mechanism for doing that: the ActionServlet's servlet entry in the web.xml deployment descriptor file. We simply need to add another initialization parameter, mapping, to tell ActionServlet which class to use for action mappings. The restriction: The class specified in the mapping element must extend ActionMapping. The ActionServlet's web.xml entry now looks like this:

  <servlet id="Servlet_1">
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
    <!-- Struts Config -->
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>mapping</param-name>
      <param-value>org.apache.struts.action.SecureActionMapping</param-value>
    </init-param>
    <!- other initialization parameters -->
    <load-on-startup>1</load-on-startup>
  </servlet>

Now when ActionServlet parses the struts-config.xml file, it creates a SecureActionMapping instance for every action defined in that file. When it encounters a set-property element for the secure property, ActionServlet uses introspection to find the secure property mutator method, setSecure(), on that SecureActionMapping instance. ActionServlet's parsing logic is also smart enough to convert the specified value into the correct Boolean value.

SecureActionServlet

In addition to the standard config and mapping initialization parameters, we must specify two new properties: our application's port numbers for the HTTP and HTTPS protocols. We specify these the same way we specify the other initialization parameters. The values we assign to the port number parameters are stored in the two properties we define for our extension to the ActionServlet class.

This new extension, called SecureActionServlet, initializes these new properties. The servlet entry in the web.xml now has the following code:

  <servlet id="Servlet_1">
    <servlet-name>action</servlet-name>
    <servlet-class>org.apache.struts.action.SecureActionServlet</servlet-class>
    <!-- Struts Config -->
    <init-param>
      <param-name>config</param-name>
      <param-value>/WEB-INF/struts-config.xml</param-value>
    </init-param>
    <init-param>
      <param-name>mapping</param-name>
      <param-value>org.apache.struts.action.SecureActionMapping</param-value>
    </init-param>
    <!- other initialization parameters -->
    <init-param>
      <param-name>http-port</param-name>
      <param-value>8080</param-value>
    </init-param>
    <init-param>
      <param-name>https-port</param-name>
      <param-value>8443</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

Note that we substituted the name of our new SecureActionServlet subclass where ActionServlet was previously specified.

The default values for the port number properties, used in cases where values are not assigned through the initialization parameters, are the standard HTTP and HTTPS port numbers of 80 and 443, respectively. SecureActionServlet implements the logic necessary to determine whether to perform a redirect for channeling an action request to the proper protocol. This logic first finds the SecureActionMapping object for a Struts action. Next, it checks the value of that object's secure property to determine whether it must execute a redirect to the specified protocol for calling the Action subclass's perform() method. All other ActionServlet behavior remains unchanged in its subclass. The full source code for SecureActionServlet appears below:

Page 2 of 2
package org.apache.struts.action;
import org.apache.struts.util.SecureRequestUtils;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
/**
 * An extension of the ActionServlet class to add properties for
 * http and https ports.  Performs logic to determine if
 * requests should be redirected to another (http or https) protocol.
 */
public class SecureActionServlet extends ActionServlet {
    protected String httpPort = "80";
    protected String httpsPort = "443";
    public void init() throws ServletException {
        super.init();
        initPorts();
    }
    /**
     * Initializes the http and http port properties
     * from init parameters specified in the web.xml file.
     * If not found there, default values remain.
     */
    protected void initPorts(){
        String value = getServletConfig().getInitParameter("http-port");
        if( value != null ){
        // Other validation code here.
            httpPort = value;
        }
        value = getServletConfig().getInitParameter("https-port");
        if( value != null ){
        // Other validation code here.
            httpsPort = value;
        }
    }
    public void setHttpsPort( String s ){
        this.httpsPort = s;
    }
    public void setHttpPort( String s ){
        this.httpPort = s;
    }
    public String getHttpsPort( ){
        return this.httpsPort;
    }
    public String getHttpPort( ){
        return this.httpPort;
    }
    /**
     * Checks whether servlet should redirect to another protocol
     * before calling the superclass process method.
     *  @param aRequest is the current request object.
     *  @param aResponse is the current response object.
     *  @throws IOException if an input/output error occurs.
     *  @throws ServletException  if a servlet error occurs.
     */
    protected void process(HttpServletRequest request,
                HttpServletResponse response
            ) throws IOException, ServletException {
        // Identify the path component we will use to select a mapping.
        String path = processPath(request);
        if (path == null) {
            if (debug >= 1)
            log(" No path available for request URI " +
                request.getRequestURI());
            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                       internal.getMessage("processPath"));
            return;
        }
        // Look up the corresponding mapping.
        SecureActionMapping mapping = (SecureActionMapping)processMapping(path, request);
        if (mapping == null) {
            if (debug >= 1)
            log(" No mapping available for path " + path);
            response.sendError(HttpServletResponse.SC_BAD_REQUEST,
                       internal.getMessage("processInvalid", path));
            return;
        }
        // Redirect to https/http if necessary.
        if( checkSsl(mapping, request, response) ){
            return ;
        }
        super.process(request, response);
    }
    /** Checks to see if SSL should be toggled for this
     *  action.
     *  @param aMapping is the mapping object for this Action.
     *  @param aRequest is the current request object.
     *  @param aResponse is the current response object.
     */
    private boolean checkSsl(SecureActionMapping aMapping,
            HttpServletRequest aRequest,
            HttpServletResponse aResponse){
        String redirectString =
                SecureRequestUtils.getRedirectString(aRequest,
                getHttpPort(),
                getHttpsPort(),
                aMapping.getSecure());
        if( redirectString != null ){
            try{
                // Redirect the page to the desired URL.
                aResponse.sendRedirect(aResponse.encodeRedirectURL( redirectString));
                return true;
            }catch(Exception ioe){
                System.out.println("IOException in redirect" + ioe.getMessage());
            }
        }
        return false;
    }
}

Figure 2 illustrates the class diagram with our new extensions.

Figure 2. UML diagram of the Struts 1.0 action-processing classes with added secure extensions

PageScheme custom tag

In my previous article, I discussed another piece of our mixed protocol solution, the sslext:pageScheme custom tag. You could use this tag to specify that a JSP should display with HTTPS by assigning a true value to the JSP's secure attribute. A false value would specify HTTP. We will implement this custom tag with its defining code and usage virtually unchanged from its description in the previous article. I include its source code and tag library descriptor, as well as the source code and tag library descriptors for all tags introduced in this article, in Resources.

SecureRequestUtils

The final part of our mixed protocol solution is the utility class SecureRequestUtils, which performs our algorithm's heavy lifting. From its introduction in the previous article, I've incorporated it here with few changes.

Solution complete?

If we stopped here, we would have finished implementing the mixed protocol solution as a Struts extension. Through careful specification of secure actions and JSPs, we could build Web applications on this framework that protect sensitive data during its transmission.

Some concerns can arise from less careful specification of secure Struts actions and JSPs. For instance, posting a large form from an HTTP-specified page to an HTTPS-specified Struts action could result in a query string from the posted request body parameters during the redirect that exceeds the browser or Web server's capacity. Also, although SSL protects the query string during the redirected transmission, users might feel uncomfortable with posted data appearing as part of the query string in the browser's location display.

We should make the solution foolproof. Fortunately, we can by again building upon the Struts infrastructure to extend some of Struts's custom tags.

Custom form tag

You can use the Struts custom tag html:form tag to render an HTML input form at runtime in the JSP. This tag's typical usage in its simplest form looks like this:

<html:form action="/submitAction" >
        <!- The form's input elements specified here -->
</html:form>

At runtime, the tag would produce the following HTML form tag:

<form name="testForm" method="POST" action="/testssl/do/submitAction ">
        <!- The form's input elements specified here -->
</form>

The name testForm originates from the form bean's name we specified for the HTML form tag immediately above in our struts-config.xml file. The default method for html:form is POST, although you can specify GET submission methods for your forms using the method attribute. Finally, the action specified in the HTML form tag immediately above comes from the action we specified in the html:form tag. We just added the Web application's context root, testssl, and our SecureActionServlet's URL mapping, /do/*, to it.

Just as in SecureActionServlet's redirect logic, we can look at the secure property's value in the SecureActionMapping object for the action specified in the html:form tag to determine whether the form should be submitted via HTTP or HTTPS. We compare that submission specification with the protocol used to transmit the page containing the form. If the submission protocol fails to match the current page's protocol, you can specify the correct submission protocol in the resulting HTML definition for the form.

We code this behavior into an html:form tag extension called sslext:form by extending the class org.apache.struts.taglib.FormTag with our own org.apache.struts.taglib.SecureFormTag class. As an example of this tag in use, assume a JSP transmitted using HTTP contained the following two forms:

<sslext:form action="/secureAction" >
        <!- The form's input elements specified here -->
</sslext:form>
<sslext:form action="/nonsecureAction" >
        <!- The form's input elements specified here -->
</sslext:form>

The resulting HTML would look like this:

<form name="testForm" method="POST"
action="https://localhost:8443/testssl/do/secureAction">
        <!- The form's input elements specified here -->
</form>
<form name="testForm" method="POST" action="/testssl/do/nonsecureAction">
        <!- The form's input elements specified here -->
</form>

The use of this new custom tag should eliminate any redirects that might otherwise have occurred during form submissions.

Custom link tag

Struts also provides a custom html:link tag. You substitute this tag for the HTML a to specify anchors or links to other Web resources. The custom tag also offers other features, such as the ability to build a query string from specified bean component properties and add that query string to the specified link.

Applying the same logic outlined for our sslext:form custom tag, we extend the html:link to exhibit the same behavior. This tag extension, called sslext:link, renders HTML a tags that link directly to the specified actions using the correct protocol. This will further minimize the number of redirects performed by our mixed protocol solution's implementation. Our original and new extension classes: org.apache.struts.taglib.FormTag and org.apache.struts.taglib.SecureFormTag, respectively.

For an example of our new sslext:link custom tag, assume a JSP transmitted using HTTPS includes the following two links:

<sslext:link page="/do/secureLinkAction" >Secure Link</sslext:link>
<br>
<sslext:link page="/do/nonsecureLinkAction" >Non-Secure Link</sslext:link>

The resulting HTML:

<a href="/testssl/do/secureLinkAction">Secure Link</a>
<br>
<a href="http://localhost:8080/testssl/do/nonsecureLinkAction">Non-Secure Link</a>

Had the same page been transmitted via HTTP, the resulting HTML would be:

<a href="https://localhost:8443/testssl/do/secureLinkAction">Secure Link</a>
<br>
<a href="/testssl/do/nonsecureLinkAction">Non-Secure Link</a>

Through the use of these two new custom tags, a Struts action request should generate a redirect as part of our solution in two instances only:

  1. When a user directly inputs a URL with an incorrect protocol into the browser location field
  2. When an action forwards directly to another action with a different secure property specification

Redirects would still be required for a JSP that utilizes the sslext:pageScheme tag to specify a protocol that fails to match the protocol of the Struts action that forwarded to it.

Extension ease amplifies Struts strength

After my first article published, many developers told me they had faced the mixed protocol problem multiple times and had longed for a solution such as the one I presented. The solution's flexibility lets you easily change a Web resource's transmission protocol specification any time during development or even after deployment. Combining that strength with the power of Struts and then building upon that framework produces an even greater tool. Because Struts is an open source framework, it relies upon the innovation and contribution from numerous developers. The ease with which you can extend Struts encourages this innovation.

In this article, we added the features of the mixed protocol solution to the Struts framework with just a few extensions to the existing Struts codebase. With a few more changes, we improved upon the original solution by extending some tags from Struts's tag library. By minimizing the number of redirects, these new extended tags improved our solution's performance. For its power and ease of extension, Struts's popularity among dynamic Web application developers should continue its rapid rise.

I would like to thank Max Cooper, Prakash Malani, and Danny Trieu for their assistance and suggestions in preparing this article.

Steve Ditlinger is a senior software engineer at eBuilt in Irvine, Calif. He has more than 14 years' experience in software development at both large and small companies in Southern California. He has extensive experience in developing enterprise, e-commerce, and other Internet application systems using Java and J2EE technologies for clients in many different industries. In addition, he currently teaches courses in Java and JDBC (Java Database Connectivity) at the California State Polytechnic University at Pomona. He holds bachelor's and master's degrees in engineering from Purdue University.

Learn more about this topic