Mix protocols transparently in Web applications

Implement HTTP and HTTPS in a safe, flexible, and easily maintainable manner

Many Web applications, especially those deployed for e-commerce, necessitate the transmission of sensitive data between the Web server and the client browser. This data could include passwords, credit card numbers, or bank account numbers -- any information users would not want divulged to the general public. To protect sensitive data during transmission, developers at Netscape Communications created Secure Sockets Layer (SSL) and its companion protocol, HTTP over Secure Sockets Layer (HTTPS). HTTPS employs SSL to protect data by encrypting it at the source, be it the server or the client, and decrypting it at the destination, thus preventing anyone monitoring Internet data transmissions from easily capturing this data. The client and server exchange public keys to enable encryption and decryption.

The encryption/decryption process comes at a performance price, however. Data throughput for a Web server transmitting via HTTPS is often as little as one-tenth that of data transmission via HTTP. For this reason, you shouldn't deploy an entire Web application under SSL. For fastest performance, deploy a Web application under HTTP and employ HTTPS only for those pages and processes that transmit sensitive data. In this article, I propose and develop a solution for implementing this protocol mixture.

Current SSL implementations: Static links

Perhaps the most prevalent approach for integrating SSL into a Web application is to specify the entire URL, including the HTTPS protocol, in those hyperlinks that lead to Webpages or servlets requiring HTTPS. This leads to HTML code like the following snippet from a page not requiring SSL:

<a href="../nonssl/insecurePage.jsp">Non-SSL link</a>
<a href="https://www.somedomain.com/ssl/securePage.jsp">SSL Link</a>

Similarly, pages requiring HTTPS should specify the HTTP protocol in hyperlinks that lead to pages or servlets that do not require the extra data protection. The following HTML would come from a page requiring SSL:

<a href="http://www.somedomain.com/nonssl/insecurePage.jsp">Non-SSL link</a>
<a href="../ssl/securePage.jsp">SSL Link</a>

Advantages

One advantage to this method: you can easily implement it during development. You need no mechanism beyond what basic HTML provides.

Disadvantages

As is often the case, what proves easy to implement during development turns into a maintenance problem in production. Changing the protocol for any particular Webpage or servlet requires that you find and edit all links to that page or servlet to specify the new protocol. For portability reasons, you should specify hyperlinks in a fashion relative to a common directory or root context. Forcing the entire URL specification in hyperlinks creates a maintenance problem when the application moves from development to deployment, or any time the domain name or root context changes.

The biggest problem with the static link implementation is that nothing prevents a user from specifying the wrong protocol by manually entering a URL into the browser. The penalty for manually specifying HTTPS for a page or servlet not requiring HTTPS: reduced performance. Far worse is the penalty for manually specifying HTTP for nonsecure access of a page that does require HTTPS: sensitive data exposure.

Current SSL implementations: Restrict access

To prevent nonsecure access of sensitive data, the Java Servlet Specification 2.2 (and 2.3) defines the user-data-constraint element of the deployment descriptor for Web applications, better known as the web.xml file. As a child of the security-constraint element, user-data-constraint contains the transport-guarantee element. This element must specify one of three protection types for communication between client and server: NONE, INTEGRAL, or CONFIDENTIAL. While a NONE designation means that the Web resource being specified requires no transport guarantees, an INTEGRAL designation indicates that the Web resource must transmit between the client and server in a way that prevents changes to the resource's data while in transit. CONFIDENTIAL means that the Web resource's data must travel in a way such that no one can observe it while in transit. Most containers -- including BEA's WebLogic Server 6.1, which we'll use in this discussion -- treat the INTEGRAL or CONFIDENTIAL designations as a requirement for SSL usage. When a Web resource is specified as INTEGRAL or CONFIDENTIAL in the web.xml file as shown below, the user cannot access that resource over HTTP:

<security-constraint>
    <web-resource-collection>
       <url-pattern>/SomeSslServlet</url-pattern>
    </web-resource-collection>
    <user-data-constraint>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
    </user-data-constraint>
</security-constraint>

If a user attempts to access such a resource over HTTP, perhaps by manually entering the URL into her browser, a page pops up informing her that she needs SSL to access the requested resource. When WebLogic is the container, this message reads, "Need SSL connection to access this resource."

Advantages

This approach combined with the static links method continues to provide the deployment advantages described for the static links method. The use of user-data-constraint in the deployment descriptor adds little complexity to an existing web.xml file. Using the deployment description in this way eliminates sensitive data exposure, which was the greatest disadvantage of using static links alone.

Disadvantages

The static link approach's other disadvantages remain. Link maintenance within pages will continue to prove troublesome for the same reasons outlined earlier. A new problem, however, might surface, where a user confronted with the given error message or even a more descriptive page might not understand the need to use HTTPS for accessing a secured Web resource. The message might intimidate or frustrate her; rather than re-enter the URL with the appropriate protocol, she might simply leave the Website.

Better solution needed

The ideal solution: an approach that automatically uses the correct protocol when accessing Web resources. This would both prevent access via an inappropriate protocol and shield users from re-entering URLs. The ideal solution should also prove easy for developers to implement and maintain.

Java Web application resource flow mechanisms

To develop our desired solution, we need to devise a process for routing users to the appropriate protocol for each Web resource. J2EE (Java 2 Platform, Enterprise Edition) provides two mechanisms that send users to another URL.

The first of these mechanisms is the forward() method found in the RequestDispatcher interface. Web applications that follow the MVC (Model-View-Controller) architecture often use this method to forward a request from a servlet to a JSP (JavaServer Page). A typical forward() instance within a servlet looks like this:

aRequest.getRequestDispatcher( "/somePage.jsp" ).forward(aRequest, aResponse);

A typical instance within a JSP, like this:

application.getRequestDispatcher( "/somePage.jsp").forward(aRequest, aResponse);

However, this limited mechanism can forward only to either another resource with the same root context or another context with the same document root, which includes the request protocol. This limitation prevents us from using the mechanism to forward a request with another protocol.

The second mechanism, the sendRedirect() method in the HttpServletResponse interface, provides the power to route to any URL with any protocol, as shown here:

aResponse.sendRedirect("http://some.otherdomain.com/aPage.jsp");

The only caveat here is that a response can only issue a redirect before it has committed. If a response attempts a redirect after committing, the sendRedirect() method will throw an IllegalStateException. With this limitation in mind, we select the sendRedirect() mechanism for use in our SSL implementation solution because of its greater flexibility in URL specification.

Solution proposal

In addition to the redirect mechanism, we will use two other methods from the Java Servlet API: We use the getScheme() method on the ServletRequest interface to determine whether a Web resource was called using the HTTP or HTTPS protocol. The getRequestUrl() method on the HttpUtils class tells us what URL requested the Web resource. (Note: This method has moved to the HttpServletRequest interface in the Servlet 2.3 specification.)

Basic solution algorithm

The fundamental algorithm for our solution:

  • Determines the protocol used to request our Web resource
  • If that protocol matches the protocol we want for this resource, it does nothing
  • If that protocol doesn't match the protocol we want, it redirects to the same URL using the correct protocol

As an example, if a user issues a request to SomeSslServlet using the URL http://www.somedomain.com/SomeSslServlet, our algorithm redirects the request to the URL https://www.somedomain.com/SomeSslServlet.

Develop the solution

The code corresponding to our algorithm, in its simplest form, appears below:

   String desiredScheme = "https" ; // or "http"
   String usingScheme = aRequest.getScheme();
   if ( !desiredScheme.equals(usingScheme) ) {
              StringBuffer url = HttpUtils.getRequestURL(aRequest);
              url.replace(0, usingScheme.length(), desiredScheme );
              aResponse.sendRedirect(
                    aResponse.encodeRedirectURL(url.toString()));
              return;
   }

The return statement after the redirect is necessary to terminate the thread executing the Web resource containing the logic. The desired scheme's specification could be hardcoded as shown here or read from an external source to allow the desired protocol's specification at deployment time for each Web resource. An external source could be a properties file, a database table, or the web.xml deployment descriptor file, as you will see next.

Use standard ports

This above implementation assumes the use of the standard HTTP and HTTPS ports of 80 and 443 respectively. When using these ports, we don't need to specify the port number in the URL request. Therefore, when using the default ports, we don't need to specify a different port number when we redirect from one protocol to the other.

Use nonstandard ports

If we want a more general solution, we must allow for nonstandard port numbers in our Web application. Rather than hardcode the port numbers, we rely on the Java Servlet Specification's convenient mechanism for defining such properties at deployment time through the deployment descriptor.

This mechanism, the context-param element in the web.xml file, allows us to define an initialization parameter for a Web application. This parameter is defined as a name-value pair that can be discovered at deployment time. Our entries in the web.xml file for defining the port numbers are:

<context-param>
    <param-name>listenPort_http</param-name>
    <param-value>7001</param-value>
</context-param>
<context-param>
    <param-name>listenPort_https</param-name>
    <param-value>7002</param-value>
</context-param>

The discovery of these values at deployment time in our application takes this form:

String httpPort = aServletContext.getInitParameter("listenPort_http");
String httpsPort = aServletContext.getInitParameter("listenPort_https");

Algorithm with configurable ports

We must modify our algorithm to allow for nonstandard server ports. The algorithm now:

  • Determines the protocol used to request our Web resource
  • If the protocol matches the protocol we want for this resource, it does nothing
  • Otherwise, it redirects to the same URL using the correct protocol, modifying the specified port number if necessary

In code, the algorithm now looks like this:

String desiredScheme = ... ;  // From external source
String usingScheme = aRequest.getScheme();
String desiredPort =  "https".equals(desiredScheme)?httpsPort: httpPort;
String usingPort =  "https".equals(usingScheme)?httpsPort: httpPort;
if ( !desiredScheme.equals(usingScheme) ){
    StringBuffer url = HttpUtils.getRequestURL(aRequest);
    url.replace(0, usingScheme.length(), desiredScheme );
    // Find the port used within the URL string
    int startIndex = url.toString().indexOf(usingPort);
    // If the port could not be found in the URL string, it must have
    // been using a default port (80 or 443 for standard or SSL, respectively)
    if( startIndex == -1 ){
        // If desired port is not a default port, insert the port number
        // into the URL
        if((!("443".equals(desiredPort) && "https".equals(desiredScheme)))
                        && (!("80".equals(desiredPort) && "http".equals(desiredScheme)))){
            // Comes before the 3rd slash
            startIndex = url.toString().indexOf("/", url.toString().indexOf("/", url.toString().indexOf("/")+1)+1);
            url.insert(startIndex, ":" + desiredPort);
        }
    }else{  // Port could be found in URL string
         // If desired port is a default port, just delete the existing
         // port specification from the URL string
         if(("443".equals(desiredPort) && "https".equals(desiredScheme))
                        || ("80".equals(desiredPort) && "http".equals(desiredScheme))){
                    url.delete(startIndex-1, startIndex + usingPort.length());
         } else {  // Desired port is not a default port
             // Replace requested port with desired port number in URL string
             url.replace(startIndex, startIndex + usingPort.length(), desiredPort );
         }
    }
    aResponse.sendRedirect(aResponse.encodeRedirectURL(url.toString()));
}
return;

Other issues

We must consider additional issues in our solution's implementation to prevent it from changing the logic or flow of any Web application in which it is implemented.

Query string

The first consideration applies to GET requests and concerns any query string that might have been specified in the request URL. The string returned from the getRequestUrl() method does not include any query string. Thus, the query string will disappear unless it is specifically added to the URL before we redirect. This requires a slight modification to our algorithm.

Request-body parameters

Similar to the query string consideration is the issue of request parameters that might appear in a POST request's body. These parameters will also disappear unless we account for them when we redirect to another protocol. We will modify our algorithm to convert these request-body parameters into a query string when we redirect. If these body parameters include sensitive data, it will remain safe during transmission, as SSL also protects the query string.

Request attributes

Web applications can also store objects in the request as attributes in a Web application. These request attributes will also disappear during a redirect. We will modify our algorithm to temporarily store these attributes in the session during the redirect.

Cookie domain name

Most browsers treat requests to different ports on the same server as requests to different domains and therefore initiate a new session each time a user requests a different port. So our users can maintain a session across requests to our application's standard and SSL ports, we must properly configure a parameter on our application server. For WebLogic 6.1, we do that by setting the cookie domain name to our Web application's domain name in the weblogic.xml file, like so:

<session-descriptor>
   <session-param>
      <param-name>CookieDomain</param-name>
      <param-value>www.somedomain.com</param-value>
   </session-param>
</session-descriptor>

Multiple redirects

The final consideration relates to a problem that appears specific to versions 5 and higher of Microsoft's Internet Explorer (IE). These browsers require multiple redirects for each redirect specified by the application. If the application specifies a redirect to a different port using a different protocol, IE performs a redirect changing only the protocol. To change the port number, we need a second redirect. This requires us to add additional complexity to our algorithm.

Final algorithm

Taking all these additional considerations into account, our algorithm now:

  • Determines the protocol and port number used to request our Web resource
  • If the protocol and port number match the protocol and port number we want for this resource, it does nothing
  • Otherwise, it checks for a query string
  • If there is no query string, it checks for request-body parameters
  • If there are request-body parameters, it converts these parameters to a query string
  • If there are request attributes, it temporarily moves them to the session in a Map object
  • Finally, it redirects to the same URL, using the correct protocol, modifying the specified port number, if necessary, and adding any existing or generated query string

The code for this new algorithm, incorporated into utility classes, looks like this.

Returning a null URL string indicates to the calling servlet method (or JSP) that a redirect is unnecessary. Otherwise, our algorithm redirects the request to the returned URL in the calling servlet method (or JSP) as shown here:

boolean isSsl = ...;  // From external source
String redirectString =
          SslUtil.getRedirectString(aRequest, getServletContext(),  isSsl);
if( redirectString != null ){
    try{
        // Redirect the page to the desired URL
        aResponse.sendRedirect(aResponse.encodeRedirectURL(redirectString));
    } catch (IOException ioe){
       // Handle appropriately
    }
    return;
}

Implementation

We implement our solution by incorporating calls to our algorithm's utilities as the first operation in every servlet and JSP within a Web application. By incorporating redirect logic into every Web resource, we keep all our links between pages and servlets relative to a common root context. The redirect logic in each resource transparently routes each request to the correct protocol, so we do not need to specify the protocol in the links. Incorporating these calls as the first operation ensures that any redirect occurs before a response has committed.

JSP implementation through a custom tag

The most efficient alternative to duplicating calls to the utility methods in every JSP is to create and use a custom tag. With a custom tag, we can reuse a single set of calls to those methods for all our JSPs. We specify the preference for HTTP or HTTPS using an attribute of our custom tag element. The class defining our custom tag is shown here:

package article.ssl.tag;
import javax.servlet.jsp.tagext.TagSupport ;
import javax.servlet.jsp.JspException ;
import javax.servlet.http.* ;
import java.io.IOException;
import article.ssl.util.*;
public class PageSchemeTag extends TagSupport {
  private String secure = "false";  // Default value
  public void setSecure(String bool) { secure = bool;  }
  public String getSecure() { return secure;  }
  public int doStartTag() throws JspException {
     // Get the request and response objects
     HttpServletRequest request = (HttpServletRequest)pageContext.getRequest();
     HttpServletResponse response = (HttpServletResponse)pageContext.getResponse();
     String redirectString =
                SslUtil.getRedirectString(request,
                pageContext.getServletContext(),
                Boolean.valueOf(getSecure()).booleanValue());
     if( redirectString != null ){
        try{
            // Redirect the page to the desired URL
            response.sendRedirect(response.encodeRedirectURL(redirectString));
        }catch(Exception ioe){
            // Handle appropriately
        }
    }
    // Skip processing the tag body.
    return SKIP_BODY ;
  }
}

The tag library descriptor entry for this custom tag is:

  <tag>
    <name>pageScheme</name>
    <tagclass>article.ssl.tag.PageSchemeTag</tagclass>
    <bodycontent>empty</bodycontent>
    <attribute>
      <name>secure</name>
      <required>true</required>
      <rtexprvalue>true</rtexprvalue>
    </attribute>
  </tag>

The custom tag's usage within a JSP is as follows:

<ssl:pageScheme secure="true" />

or:

<ssl:pageScheme secure="false" />

Servlet implementation through an application base class

To efficiently incorporate the redirect logic into every servlet in a Web application, we create an abstract servlet class to serve as the base class for all the Web application's servlets. The base class contains a method that calls the methods that perform the redirect logic. The base class also specifies abstract methods that must be defined in the servlets extending this class. The code for the servlet base class looks like this:

package article.ssl.servlet;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.*;
import java.io.*;
import java.text.*;
import article.ssl.util.*;
public abstract class BaseAppServlet extends HttpServlet{
   public void doPost(HttpServletRequest aRequest, HttpServletResponse aResponse) throws IOException, ServletException
   {
      processRequest(aRequest, aResponse);
   }
   public void doGet(HttpServletRequest aRequest, HttpServletResponse aResponse) throws IOException, ServletException
   {
      processRequest(aRequest, aResponse);
   }
   public void  processRequest(HttpServletRequest aRequest, HttpServletResponse aResponse)  throws ServletException, IOException
   {
      if( checkSsl(aRequest, aResponse) ){
         return;
      }
      doProcessRequest(aRequest, aResponse);
   }
    private boolean checkSsl( HttpServletRequest aRequest, HttpServletResponse aResponse){
        boolean isSsl = getIsSsl();
        String redirectString = SslUtil.getRedirectString(aRequest, getServletContext(), isSsl);
        if( redirectString != null ){
            try{
                // Redirect the page to the desired URL
                aResponse.sendRedirect(
                    aResponse.encodeRedirectURL(redirectString));
                return true;
            }catch(Exception ioe){
               // Handle appropriately
            }
        }
        return false;
    }
   abstract protected void doProcessRequest(HttpServletRequest aRequest, HttpServletResponse aResponse)  throws ServletException;
   abstract protected boolean getIsSsl();
}

A class extending the BaseAppServlet class provides the implementation for the abstract methods doProcessRequest(), where the servlet completes its work, and getIsSsl(), which returns a flag indicating whether the servlet should be requested under HTTP or HTTPS.

Implementation within Struts

Based on the MVC design paradigm, Struts is a Web application framework developed within the Apache Software Foundation's Jakarta Project. Typically, in an MVC framework, a servlet, acting as controller, performs some operation, such as a database query. The servlet then forwards the data from the query, passing it to a JSP serving as the view for the data, or model. Struts defines a mechanism that allows for a deployment-time definition of the logical flow among Web resources using a configuration file and a servlet-like Action class. Struts also defines custom tag libraries that simplify form creation and processing, as well as bean component use within JSPs. To take advantage of these features, we will adapt our solution to work within the Struts framework.

The struts-config.xml configuration file

The struts-config.xml configuration file contains the mapping of the logical flows among Web resources. This file contains information about every action in the Web application formatted within a series of action tag elements. This information includes the Web resources to which an action forwards control and the conditions under which it does so.

The action element allows developers to use a parameter attribute for their own purposes. We use this attribute to specify whether a particular action utilizes HTTP or HTTPS. In our implementation, if the parameter attribute has a value of secure, then we'll process the action request via HTTPS. Otherwise, we'll use HTTP. Implementing the struts-config.xml file in this way allows us to change the protocols used by each action in the application at deployment time. One action element from a struts-config.xml file appears like this:

    <action path="/viewSignUp"
            type="com.someapp.action.ViewSignUpAction"
            parameter="secure" >
      <forward name="success" path="signupPage.jsp" />
    </action>

JSPs

JSPs within Struts differ from other JSPs only in the availability of the Struts custom tags. We implement our pageScheme custom tag in the same manner shown previously for ordinary JSPs.

Actions

Classes that extend the Struts-defined Action class handle the controller role within Struts. Each class must define a perform() method, which functions much like the doPost() and doGet() methods usually defined for a servlet. To implement calls to our redirect logic in Action-based classes, we again define a base class that every action in our Web application extends. The base class's logic determines whether an action needs to use HTTP or HTTPS. The base class will then use that information to call the redirect logic, as shown here:

package article.ssl.struts;
import org.apache.struts.action.*;
import javax.servlet.http.*;
import article.ssl.util.*;
public abstract class BaseAppAction extends Action {
    public static final String SECURE = "secure";
    public abstract ActionForward doPerform (
        ActionMapping aMapping,
        ActionForm aForm,
        HttpServletRequest aRequest,
        HttpServletResponse aResponse
    ) throws Exception;
    private boolean checkSsl(ActionMapping aMapping,
            HttpServletRequest aRequest,
            HttpServletResponse aResponse){
        String redirectString =
                SslUtil.getRedirectString(aRequest,
                getServlet().getServletContext(),
                SECURE.equals(aMapping.getParameter()));
        if( redirectString != null ){
            try{
                // Redirect the page to the desired URL
                aResponse.sendRedirect(
                    aResponse.encodeRedirectURL(redirectString));
                return true;
            }catch(Exception ioe){
                 // Handle appropriately
            }
        }
        return false;
    }
    public ActionForward perform(
        ActionMapping aMapping,
        ActionForm aForm,
        HttpServletRequest aRequest,
        HttpServletResponse aResponse
    ) {
        try {
            if( checkSsl(aMapping, aRequest, aResponse) ){
                return null;
            }
            ActionForward aAF =  doPerform(aMapping, aForm, aRequest, aResponse);
            return ((aAF == null) ? aMapping.findForward("success") : aAF);
        } catch (Exception e) {
            return aMapping.findForward("exception");
        }
    }
}

Proposed integration into Struts

Our solution's best implementation would integrate the solution into the Struts framework itself. We could integrate the pageScheme custom tag into the Struts tag library with little or no difficulty.

Struts defines the ActionServlet class, an instance of which reads the Web application's struts-config.xml file at startup time. This instance also controls the execution of all the Web application's actions. Complete integration into Struts would require an additional attribute for the action element in struts-config.xml and an extension to the ActionServlet class. When an action is requested, the new attribute for the action element would specify which protocol to use. We could add two initialization parameters for ActionServlet in the web.xml file for defining the HTTP and HTTPS listening ports. We could add code to ActionServlet to discover these parameter values. Armed with this information and the specification of each action's transmission protocol, the ActionServlet itself could call the redirection logic.

These few changes to Struts would make our SSL implementation solution integral to the framework. In this way, our SSL solution implementation becomes nearly transparent to developers using Struts.

Protect data while enhancing performance

Through the redirect mechanism defined in the Servlet API, we can implement a mixed HTTP/HTTPS Web application in a manner completely transparent to the user. In this way, Web resources requiring SSL are guaranteed transmission via HTTPS. Web resources that do not require SSL are guaranteed transmission via HTTP. This implementation protects sensitive data and enhances Website performance. Through JSP custom tags and a servlet base class, this capability adds little complexity to Web application development. By combining this solution with an external configuration method, Web application maintenance becomes much simpler than other commonly employed solutions. You can also implement this solution using the Struts Web application framework. While this solution works well with Struts, integrating it into Struts as an extension to the framework would prove ideal.

Acknowledgments

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

Steve Ditlinger is a senior software engineer at eBuilt, Inc. in Irvine, Calif. He has more than 13 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

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