Chapter 11. Security

Introduction

Security is one of those aspects of development that often gets relegated to the end of the programming process. It seems like customers expect security features can be bolted on to an application. What makes things worse is you can implement security in many ways. When it comes to building security into an application, determine the requirements before you start coding. Additionally, it can be tempting to let technology drive requirements. Some technologies are flexible and let you get away with this, but when it comes to security, this is not the case. Choosing one security approach or technology over another without understanding what the user wants and needs can leave you scrambling at the end of the development cycle. You don’t want to be recoding your entire security layer before your application goes into production.

Security features center around two basic concepts: authentication and authorization. Users authenticate to the system to prove they are who they say they are. Authorization allows or disallows access to certain application features. Authentication requirements are usually specific and concrete:

Anybody can access the welcome page of the application. But if a users attempt to access any other page they will have to log in with a username and password.

Authorization needs tend to be broad and vague. In many cases, authorization rules can be complex, particularly if the authorization requirements are based on hierarchical or matrix-style organizations:

Users can only look at the data they own. The user’s manager can look at any information for users that work for him. Administrators can look at anybody’s data but can only modify the data if approved by a manager.

Before you start coding to meet these requirements, ensure you understand what the various approaches to security can and cannot do. Security mechanisms for J2EE and Java-based applications fall into two broad categories: container-managed security and application-managed security. Container-managed security is specified as part of the J2EE and Servlet specifications. Any spec-compliant application server—such as Tomcat, JBoss, Weblogic, or Websphere—supports container-managed security. Since Tomcat isn’t an EJB container, it supports container-managed security as specified in the Servlet specification. Full-blown J2EE containers will support container-managed security for EJB applications and web applications.

Many think container-managed security is too restrictive. It’s somewhat ironic that using container-managed security can lead to portability issues. The reason is that each container implements container-managed security in different ways. For example, each container has its own way of configuring security realms. A security realm provides data to the container used to authenticate and authorize users. This data could come from a database, an LDAP server, a flat file, or some other storage repository. If you use container-managed security, you’ll have to make changes to your application’s configuration if you want to deploy the application on a different application server. Several recipes related to setting up container-managed security are presented in this chapter.

With application-managed security, you’re responsible for developing the security features of your specifications. You’ll have to write more code; however, you won’t have to compromise on the requirements. Many applications manage their own security requirements for this reason alone. Servlet filters, available on containers that support the Servlet 2.3 specification, provide a great vehicle for implementing application-managed security. This chapter provides many recipes focused on application-managed security. These recipes range from the use of base Actions to full-blown custom filters for authentication and authorization.

SecurityFilter is an open source servlet filter that provides the convenience and features of container-managed security yet allows the flexibility of application-managed security. This chapter includes a recipe that shows you how to use this powerful tool.

The Solutions and code samples in this chapter provide examples that demonstrate various approaches to security. As you look at the Recipes in this chapter, keep in mind that Security is rarely something a single solution can cover. Instead, you may want to employ a combination of solutions to fulfill your requirements.

11.1. Securing Actions Using a Base Action

Problem

You need to ensure that users are logged in before they can access certain actions.

Solution

Create a base Action, like the one shown in Example 11-1, which implements the security policy.

Example 11-1. Enforcing authentication with a base action
package com.oreilly.strutsckbk.ch11;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.webapp.example.Constants;
import org.apache.struts.webapp.example.User;

public abstract class SecureAction extends Action {
    
    // final so cannot be overridden
    public final ActionForward execute(ActionMapping mapping, 
                                 ActionForm form,
                                 HttpServletRequest request, 
                                 HttpServletResponse response) 
            throws Exception {
        HttpSession session = request.getSession( );
        User user = (User) session.getAttribute(Constants.USER_KEY);
 
        // send back to the logon page if no user
        if (user == null) return (mapping.findForward("logon"));

        return doExecute(mapping, form, request, response, user);
    }

    public abstract ActionForward doExecute(ActionMapping mapping, 
                                            ActionForm form,
                                            HttpServletRequest request, 
                                            HttpServletResponse response, 
                                            User user) throws Exception;
}

Concrete Actions that require this policy extend the base SecureAction, shown in Example 11-2.

Example 11-2. SecureAction source code
package com.oreilly.strutsckbk.ch11;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
import org.apache.struts.webapp.example.User;

public class TestSecureAction extends SecureAction {

    public ActionForward doExecute(ActionMapping mapping, ActionForm form,
            HttpServletRequest request, HttpServletResponse response, 
            User user)
            throws Exception {
        // do real work here
        
        return mapping.findForward("success");
    }
}

Discussion

The base SecureAction is an abstract class that wraps the execute( ) method with the security policy. In the Solution, the policy is simple. The User object is retrieved from the HttpSession; if this object is not found, then the user must not be logged in; control is forwarded to “logon.” Otherwise, the abstract doExecute( ) method is called. In addition to the standard execute( ) arguments, the User object is passed through. Your concrete subclasses then implement doExecute( ) instead of execute( ).

Enforcing security through a base SecureAction is an understandable approach. However, this solution requires that every concrete Action must subclass SecureAction. If you are using the DispatchAction or other Struts built-in Actions, you’ll need to create your own subclasses for these that perform the security checks.

In a team environment, you’ll need to ensure all developers abide by these rules. More importantly, the base Action approach only provides security for HTTP requests that go through your base Action. It doesn’t provide any security for directly accessed JSPs, static HTML pages, and other resources accessed outside of Struts or through a different Action. If you ensure all requests go through your base Action, then this may not be an issue; however, many applications are not written in such a way.

See Also

The Base Action technique is discussed in Programming Jakarta Struts by Chuck Cavaness (O’Reilly).

The Struts JavaDoc for the Action class can be found at http://struts.apache.org/api/org/apache/struts/action/Action.html.

11.2. Checking for User Login on Any Struts Reques t

Problem

You want to be able to check if a user is logged in for any request made to an action in your struts-config.xml file, but you don’t want to have to subclass a custom base Action class.

Solution

Create a custom request processor, overriding the processPreprocess( ) or the processActionPerform( ) method. The custom request processor shown in Example 11-3 retrieves the user object from the HTTP session. If this object is null, an HTTP error response of 403 (Forbidden) is returned.

Example 11-3. Overriding the processPreprocess( ) method
package org.apache.struts.action;

import java.io.IOException;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.webapp.example.Constants;
import org.apache.struts.webapp.example.User;

public class CustomRequestProcessor1 extends RequestProcessor {

    protected boolean processPreprocess(HttpServletRequest request,
            HttpServletResponse response) {
        HttpSession session = request.getSession( );
        User user = (User) session.getAttribute(Constants.USER_KEY);
        if (user == null) {
            try {
                response.sendError(403, "User not logged in");
            } catch (IOException e) {
                log.error("Unable to send response");
            }
            return false;
        }
        
        return true;
    }
}

If you need to use the Struts objects passed to an Action’s execute() method, such as the ActionForm and ActionMapping, override the processActionPerform( ) method as shown in Example 11-4. In this example, if the user object is null, control is forwarded to the logon Struts forward; otherwise, the base RequestProcessor.processActionPerform() method is called to continue normal processing.

Example 11-4. Overriding the processActionPerform( ) method
package org.apache.struts.action;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.webapp.example.Constants;
import org.apache.struts.webapp.example.User;

public class CustomRequestProcessor2 extends RequestProcessor {

    protected ActionForward processActionPerform(HttpServletRequest request,
            HttpServletResponse response, Action action, ActionForm form,
            ActionMapping mapping) throws IOException, ServletException {
        HttpSession session = request.getSession( );
        User user = (User) session.getAttribute(Constants.USER_KEY);
        if (user == null) {
            return mapping.findForward("logon");
        } else {
            return super.processActionPerform(request, response, action, form,
                    mapping);            
        }
    }
}

You deploy the custom request processor using the controller element in the struts-config.xml file:

<controller type="com.oreilly.strutsckbk.ch11.CustomRequestProcessor1" />

Discussion

The Solutions shown in this recipe allow you to enforce a security policy across any requests handled by Struts without having to create and extend a special base Action. The first approach (Example 11-3) overrides the processPreprocess( ) method of the Struts RequestProcessor. This method is a general-purpose preprocessing method. The implementation in the base RequestProcessor is a no-op. You return true from this method to continue normal processing. If you return false, the RequestProcessor assumes that the response has been written and it aborts normal processing. In Example 11-3, the method checks for a user object in the session. If one is found, the method returns true; otherwise, the method sends the HTTP error code of 403 (Forbidden).

The second approach overrides the processActionPerform( ) method. Like the first approach, the user object is retrieved from the HTTP session. Unlike the first approach, however, Struts API objects are available. If the user object isn’t found, you call the findForward() method on the ActionMapping to forward to “logon.” Otherwise, you call the super.processActionPerform( ) method where the base RequestProcessor calls action.execute( ) and handles any thrown exception:

protected ActionForward processActionPerform(HttpServletRequest request,
                         HttpServletResponse response,
                         Action action,
                         ActionForm form,
                         ActionMapping mapping)
        throws IOException, ServletException {

    try {
         return (action.execute(mapping, form, request, response));
    } catch (Exception e) {
        return (processException(request, response,
                                 e, form, mapping));
    }
}

The two approaches shown here apply the security policy to all Actions. If needed, you can customize the Solution so the login check is selectively applied. In the first approach, which overrides processPreprocess( ), you could look for a special request parameter or attribute that indicates if the login check is required. In the second approach, you could employ a custom ActionMapping containing a property indicating if the action was secured or not.

Tip

Some Struts applications require a custom RequestProcessor. Tiles applications, for example, use the TilesRequestProcessor. To implement this Solution, you’ll need to extend the custom RequestProcessor currently used instead of the base Struts RequestProcessor.

See Also

Like using a base Action, directly accessed JSPs and static HTML pages aren’t secured by the Solution. These resources can be selectively secured using a custom JSP tag as shown in Recipe 11.3.

The Struts RequestProcessor also processes roles. You can override the processRoles() method to achieve custom handling, as shown in Recipe 11-4. You can learn about additional RequestProcessor methods in the Struts User’s Guide “Controller” section at http://struts.apache.org/userGuide/building_controller.html.

Custom action mappings are discussed in Recipe 2.8.

11.3. Securing a JSP Page

Problem

You want to ensure users can access a JSP page only if they are logged in.

Solution

Use a custom JSP tag, like the checkLogon tag from the Struts Mail Reader example application, on pages that require users to be logged in. The checkLogon tag is shown in Example 11-5.

Example 11-5. Struts-example check logon tag
package org.apache.struts.webapp.example;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import org.apache.struts.config.ModuleConfig;

/**
 * Check for a valid User logged on in the current session.  If there is no
 * such user, forward control to the logon page.
 *
 * @author Craig R. McClanahan
 * @author Marius Barduta
 * @version $Revision: 1.5 $ $Date: 2005/03/21 18:08:09 $
 */

public final class CheckLogonTag extends TagSupport {

    // --------------------------------------------------- Instance Variables

    /**
     * The key of the session-scope bean we look for.
     */
    private String name = Constants.USER_KEY;

    /**
     * The page to which we should forward for the user to log on.
     */
    private String page = "/logon.jsp";

    // ----------------------------------------------------------- Properties

    /**
     * Return the bean name.
     */
    public String getName( ) {
       return (this.name);
    }

    /**
     * Set the bean name.
     *
     * @param name The new bean name
     */
    public void setName(String name) {
       this.name = name;
    }

    /**
     * Return the forward page.
     */
    public String getPage( ) {
       return (this.page);
    }

    /**
     * Set the forward page.
     *
     * @param page The new forward page
     */
    public void setPage(String page) {
       this.page = page;
    }

    // ----------- Public Methods -----------------

    /**
     * Defer our checking until the end of this tag is encountered.
     *
     * @exception JspException if a JSP exception has occurred
     */
    public int doStartTag( ) throws JspException {
       return (SKIP_BODY);
    }

    /**
     * Perform our logged-in user check by looking for the existence of
     * a session scope bean under the specified name.  If this bean is not
     * present, control is forwarded to the specified logon page.
     *
     * @exception JspException if a JSP exception has occurred
     */
    public int doEndTag( ) throws JspException {
    
        // Is there a valid user logged on?
        boolean valid = false;
        HttpSession session = pageContext.getSession( );
        if ((session != null) && (session.getAttribute(name) != null)) {
            valid = true;
        }
    
        // Forward control based on the results
        if (valid) {
            return (EVAL_PAGE);
        } else {
            ModuleConfig config =
                (ModuleConfig) pageContext.getServletContext( ).getAttribute(
                    org.apache.struts.Globals.MODULE_KEY);
            
                try {
                    pageContext.forward(config.getPrefix( ) + page);
                } catch (ServletException e) {
                    throw new JspException(e.toString( ));
                } catch (IOException e) {
                    throw new JspException(e.toString( ));
                }     
            return (SKIP_PAGE);
        }
    }

    /**
     * Release any acquired resources.
     */
    public void release( ) {
        super.release( );
        this.name = Constants.USER_KEY;
        this.page = "/logon.jsp";
    }
}

Include the tag at the start of a page that requires users to be logged in. Example 11-6 lists the mainMenu.jsp taken from the Struts Mail Reader example application.

Example 11-6. Using the checkLogon tag on a JSP page
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/tags/app" prefix="app" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%-- Check if the user is logged in and redirect to logon if not --%>
<app:checkLogon/>
<html>
<head>
<title><bean:message key="mainMenu.title"/></title>
<link rel="stylesheet" type="text/css" href="base.css" />
</head>
<h3><bean:message key="mainMenu.heading"/> <bean:write name="user" 
property="fullName" /></h3>
<ul>
<li><html:link action="/EditRegistration?action=Edit"><bean:message 
key="mainMenu.registration"/></html:link></li>
<li><html:link forward="logoff"><bean:message key="mainMenu.logoff"/>
</html:link></li>
</ul>
</body>
</html>

Discussion

If you use directly accessed JSP pages, you will need a mechanism to secure those pages. With a custom JSP tag, you can create the logic in one place and reuse the functionality throughout your application. The checkLogon tag, shown in Example 11-5 and applied in Example 11-6, attempts to retrieve an object from the HTTP session stored under a certain name. The name property defaults to the value defined by Constants.USER_KEY. If the object isn’t found, the tag forwards to a module-relative page specified by the page property. This value defaults to /logon.jsp.

You can use this tag in your own applications even if you store the user under a different name in the session and you want to forward to a different page. In the following snippet, the user object is stored under the name user. If the object cannot be found, the tag redirects to the Register action:

               ...
<%-- Check if there's a user and redirect to registration if not --%>
               <app:checkLogon name="user" page="/Register.do"/>
               ...

Like using a base Action, this custom JSP tag only protects JSP pages on which it is included. It does not provide security for actions, static HTML pages, or other web resources.

Tip

If you implement a static HTML page as a JSP page, you can secure the page using the checkLogon tag.

See Also

Recipe 11.1 Section 11.1 shows you how to secure Actions in the same way that the Solution secures JSP pages.

Recipe 11.6 shows a more comprehensive mechanism, applicable to any web resource, for checking that a user is logged in.

11.4. Restricting Actions by Role

Problem

You want to allow a user to access an action if that user has a specific role.

Solution

Use the roles attribute of the action element to specify the roles that are permitted to use the action:

<!-- Display all users -->
<action    path="/ViewUsers"
        forward="/view_users.jsp"
          roles="manager,sysadmin"
/>

Discussion

Struts actions, configured via the action element in the struts-config.xml file, can be restricted to certain roles using the roles attribute. This attribute accepts a comma-separated list of role names. When a request is received for the action, the RequestProcessor.processRoles( ) method checks that the user has at least one of the roles specified. If the user doesn’t have one of the roles, the HTTP 403 error (Forbidden) is sent; otherwise, processing continues normally. Here is the processRoles( ) method from the Struts RequestProcessor:

protected boolean processRoles( HttpServletRequest request,
                                HttpServletResponse response,
                                ActionMapping mapping )
        throws IOException, ServletException {

    // Is this action protected by role requirements?
    String roles[] = mapping.getRoleNames( );
    if ((roles == null) || (roles.length < 1)) {
        return (true);
    }

    // Check the current user against the list of required roles
    for (int i = 0; i < roles.length; i++) {
        if ( request.isUserInRole(roles[i]) ) {
            if (log.isDebugEnabled( )) {
                log.debug(" User '" + request.getRemoteUser( ) +
                    "' has role '" + roles[i] + "', granting access");
            }
            return (true);
        }
    }

    // The current user is not authorized for this action
    if (log.isDebugEnabled( )) {
        log.debug(" User '" + request.getRemoteUser( ) +
            "' does not have any required role, denying access");
    }

    response.sendError(
        HttpServletResponse.SC_FORBIDDEN,
        getInternal( ).getMessage("notAuthorized", mapping.getPath( )));

    return (false);

}

The Struts RequestProcessor determines if a user has a role using the HttpServletRequest.isUserInRole( ) method. This method can only be used if you are using container-managed security (or if you use a Solution such as the one in Recipe 11.10). For application-managed security, you can create your own custom RequestProcessor that overrides the processRoles method.

In Example 11-7, the processRoles( ) method retrieves a User object from the HTTP session. Then, the hasRole( ) method is called on this object to determine if the user has at least one of the required roles.

Example 11-7. Custom role-handling RequestProcessor
package com.oreilly.strutsckbk.ch11;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import org.apache.struts.action.*;
import org.apache.struts.webapp.example.User;

public class RoleRequestProcessor extends RequestProcessor {
    protected boolean processRoles( HttpServletRequest request,
                                    HttpServletResponse response, 
                                    ActionMapping mapping )
        throws IOException, ServletException
    {
        // Is this action protected by role requirements? If not, return true.
        String roles[] = mapping.getRoleNames( );
        if ((roles == null) || (roles.length < 1)) {
            return true;
        }

        // Check the current user against the list of required roles
        HttpSession session = request.getSession( );
        User user = (User) session.getAttribute("user");
        if (user == null) {
            return false;
        }
        for (int i = 0; i < roles.length; i++) {
            if (user.hasRole(roles[i])) {
                return (true);
            }
        }

        // The user does not have one of the roles; send an error
        response.sendError( HttpServletResponse.SC_BAD_REQUEST,
                            getInternal( ).getMessage("notAuthorized",
                            mapping.getPath( )));
        return (false);
    }
}

This custom request processor is deployed using the controller element in the struts-config.xml file:

<controller processorClass="com.oreilly.strutsckbk.ch11.
RoleRequestProcessor"/>

The customization, shown in Example 11-7, used to code the processRoles( ) method to perform checks can be as complex as you want. You have complete access to the servlet request and response, servlet context, HTTP session, and the ActionMapping.

Tip

Since container-managed security typically supports a simple flat role structure, using application-managed security along with a custom request processor allows you to handle more complex hierarchical schemes. Because you have access to the servlet response, you can redirect a user to a different URL if that user doesn’t pass the security test.

See Also

You can learn about additional RequestProcessor methods in the Struts User’s Guide “Controller” section at http://struts.apache.org/userGuide/building_controller.html.

If you want to implement container-managed security for your application, see Recipe 11.9.

11.5. Implementing “Remember Me” Logins

Problem

You want to provide a “remember me” feature so a user’s username and password are prefilled on the logon form if that user has logged on before.

Solution

In your Action that logs a user in, create persistent cookies containing the user’s base-64 encoded username and password. The private saveCookies( ) and removeCookies( ) methods shown in Example 11-8 manipulate the cookies as needed.

Example 11-8. An Action that stores or removes cookies
package com.oreilly.strutsckbk.ch11;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;

import com.oreilly.servlet.Base64Encoder;

public final class MyLogonAction extends Action {

    public ActionForward execute( ActionMapping mapping, 
                                  ActionForm form,
                                  HttpServletRequest request, 
                                  HttpServletResponse response) 
            throws Exception {

        HttpSession session = request.getSession( );

        ActionErrors errors = new ActionErrors( );

        String username = (String) PropertyUtils.getSimpleProperty(form, 
                                   "username");
        String password = (String) PropertyUtils.getSimpleProperty(form, 
                                   "password");
        boolean rememberMe = ((Boolean) PropertyUtils.getSimpleProperty(
                                   form, "rememberMe")).booleanValue( );

        // Call your security service here
        //SecurityService.authenticate(username, password);

        if (rememberMe) {
            saveCookies(response, username, password);
        } else {
            removeCookies(response);
        }
        
        session.setAttribute("username", username);

        return mapping.findForward("success");
    }
    
    private void saveCookies(HttpServletResponse response, String 
                             username, String password) {
        Cookie usernameCookie = new Cookie("StrutsCookbookUsername",
                                           Base64Encoder.encode(username));
        usernameCookie.setMaxAge(60 * 60 * 24 * 30); // 30 day expiration
        response.addCookie(usernameCookie);
        Cookie passwordCookie = new Cookie("StrutsCookbookPassword",
                                           Base64Encoder.encode(password));
        passwordCookie.setMaxAge(60 * 60 * 24 * 30); // 30 day expiration
        response.addCookie(passwordCookie);
    }
    private void removeCookies(HttpServletResponse response) {
        // expire the username cookie by setting maxAge to 0
        // (actual cookie value is irrelevant)
        Cookie unameCookie = new Cookie("StrutsCookbookUsername", "expired");
        unameCookie.setMaxAge(0);
        response.addCookie(unameCookie);

        // expire the password cookie by setting maxAge to 0
        // (actual cookie value is irrelevant)
        Cookie pwdCookie = new Cookie("StrutsCookbookPassword", "expired");
        pwdCookie.setMaxAge(0);
        response.addCookie(pwdCookie);
    }
}

When a user goes to the logon page, fill the username and password fields with values from the cookie, decoded from base-64, using the Struts bean:cookie tag, as shown in Example 11-9.

Example 11-9. Setting logon form field values from cookies
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="com.oreilly.servlet.*" %>
<%@ taglib uri="/tags/struts-bean" prefix="bean" %>
<%@ taglib uri="/tags/struts-html" prefix="html" %>

<html>
<head>
<title>Struts Cookbook - Cookie Logon</title>
</head>
<body>
<html:errors/>

<html:form action="/SubmitCookieLogon" focus="username">
  <bean:cookie id="uname" name="StrutsCookbookUsername" value=""/>
  <bean:cookie id="pword" name="StrutsCookbookPassword" value=""/>
  <table border="0" width="100%">

  <tr>
    <th align="right">
      <bean:message key="prompt.username"/>:
    </th>
    <td align="left">
      <html:text property="username" size="16" maxlength="18"
          value="<%=Base64Decoder.decode(uname.getValue( ))%>"/>
    </td>
  </tr>

  <tr>
    <th align="right">
      <bean:message key="prompt.password" bundle="alternate"/>:
    </th>
    <td align="left">
      <html:password property="password" size="16" maxlength="18"
                    redisplay="false" 
    </td>
  </tr>

  <tr>
    <th align="right">
      <bean:message key="prompt.rememberMe"/>:
    </th>
    <td align="left">
      <html:checkbox property="rememberMe"/>
    </td>
  </tr>

  <tr>
    <td align="right">
      <html:submit property="Submit" value="Submit"/>
    </td>
    <td align="left">
      <html:reset/>
    </td>
  </tr>

</table>

</html:form>

</body>
</html>

Discussion

A cookie consists of a name-value data pair that can be sent to a client’s browser and then read back again at a later time. Browsers provide security for cookies so a cookie can only be read by the server that originally created it. Cookies must have an expiration period.

Warning

Though cookies are in widespread use and are supported by modern browsers, they do pose a privacy risk. Most browsers allow the user to disable them. You can design your web application to use cookies to improve the user experience, but you shouldn’t require users to use cookies.

The logon Action of Example 11-8 retrieves the username and password from the logon form. This form includes the true/false property rememberme, which indicates if the users want their login credentials remembered. If users want to be remembered, they check the checkbox for the rememberme property. In the MyLogonAction—if rememberme is true—the cookies are created and saved in the response. If rememberme is false, the cookies for username and password have their maxAge set to 0, effectively removing them from the response.

The bean:cookie tags used in Example 11-9 retrieve the cookie values from the request and store them in scripting variables. These tags specify the empty string (“”) as the default value in case cookies are disabled. The initial values for the login form fields are set to the values from the scripting variables.

The Solution shown here does not address cookie security issues. For a production system, the data sent in the cookies should be encrypted. A simple encryption scheme, such as MD5 or a variant of the Secure Hash Algorithm (SHA), can be used to encrypt the cookie value when it is created. Since the server creates the cookie and is the only party that can legitimately use the data, it can encrypt and decrypt the data using the algorithm of its own choosing. Alternatively, you can send the cookies only over HTTPS, thereby providing encryption/decryption at the transport level.

See Also

You can use cookies to log in a user automatically; in other words, if users have a cookie(s) with valid credentials for the web application they don’t have to submit the login form at all. The automatic login approach is shown in Recipe 11.7.

Recipe 11.10 shows how to use the open source SecurityFilter software to implement “remember me” functionality. Its implementation includes support for cookie encryption and other settings.

Java Servlet Programming by Jason Hunter (O’Reilly) covers servlet development from top to bottom, including the Cookie APIs. The Base64 encoder and decoder used in this recipe are part of the companion com.oreilly.servlet classes available from http://www.servlets.com/cos/.

The foundation of Java server-side cookie handling is the Servlet Specification and API, available for download from http://java.sun.com/products/servlet/download.html.

The JavaBoutique has a nice tutorial on server-side cookie handling found at http://javaboutique.internet.com/tutorials/JSP/part09/. Alexander Prohorenko has written a good article on cookie security issues for O’Reilly’s ONLamp.com site at http://www.onlamp.com/pub/a/security/2004/04/01/cookie_vulnerabilities.html.

11.6. Ensuring Security Across Your Entire Application

Problem

You need to verify the user is logged in and authenticated when a request is received for any URL path of your web application.

Solution

Use an authentication servlet filter, such as the one in Example 11-10, which checks for a User object in the session.

Example 11-10. A servlet filter that checks if the user is logged in
package com.oreilly.strutsckbk.ch11.ams;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

public class AuthenticationFilter implements Filter {

    private String onFailure = "logon.jsp";
    private FilterConfig filterConfig;
    
    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        onFailure = filterConfig.getInitParameter("onFailure");
    }
   
    public void doFilter(ServletRequest request,
                       ServletResponse response,
                       FilterChain chain) 
                 throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        
        // if the requested page is the onFailure page continue
        // down the chain to avoid an infinite redirect loop        
        if (req.getServletPath( ).equals(onFailure)) {
            chain.doFilter(request, response);
            return;
        }
        
        // get the session or create it
        HttpSession session = req.getSession( ); 
        User user = (User) session.getAttribute("user");
        if (user == null) {
            // redirect to the login page 
            res.sendRedirect(req.getContextPath( )+onFailure);
        }
        else {
            chain.doFilter(request, response);
        }
    }

    public void destroy( ) {
    }
}

Tip

This filter assumes that a separate Action authenticates the user and creates the User object.

Discussion

Servlet filters provide a convenient way to apply across-the-board processing of a servlet request. Servlet filters were introduced in the Servlet 2.3 specification. Most all containers support Servlet 2.3, so you can probably use them in your application. The filter looks for a User object from the HttpSession. If the object is found, then processing continues normally. If the object isn’t found, then a redirect to the page specified by the onFailure initialization parameter is sent in the response.

You create a filter by implementing the Filter interface, declare the filter in your web.xml file, and map URLs to it using URL patterns (see the Sidebar 11-1). In Example 11-11, the filter is declared by the filter element. The init-param element specifies the context-relative path of the URL to redirect if authentication fails.

Example 11-11. Filter declaration and mapping (partial)
<filter>
    <filter-name>AuthenticationFilter</filter-name>
    <filter-class>
         com.oreilly.strutsckbk.ch11.ams.AuthenticationFilter
    </filter-class>
    <init-param>
        <param-name>onFailure</param-name>
        <param-value>/logon.jsp</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>AuthenticationFilter</filter-name>
    <url-pattern>/reg/*</url-pattern>  
</filter-mapping>

The value of the url-pattern in the filter-mapping determines the URLs to be filtered. In this case, the filter mapping indicates users must be logged in before they can access a URL that matches the url-pattern /reg/*. URLs that match this pattern include /reg/Main.do, /reg/viewReg.jsp, /reg/help.html, and /reg/sub/viewSub.jsp.

See Also

Recipe 11.7 shows a servlet filter that authenticates using cookies. Recipe 11.8 presents a servlet filter that can be used for authorization.

Recipe 11.10 shows how to use the open source SecurityFilter software for providing functionality similar to the filter presented in this recipe.

Java Servlet Programming by Jason Hunter (O’Reilly) covers servlet filters in-depth. Sun’s Java site has a good article on the essentials of servlet filters. It can be found at http://java.sun.com/products/servlet/Filters.html.

11.7. Allowing a User to Log in Automatically

Problem

You want to allow users to be logged in automatically if they have valid credentials stored in a cookie(s).

Solution

Use a servlet filter, such as the one shown in Example 11-12, that looks for cookies containing the user’s credentials. The credentials are used to authenticate the user. If the authentication succeeds, the user is automatically logged in; otherwise, the user will be prompted to login.

Example 11-12. Cookie authentication filter for automatic login
package com.oreilly.strutsckbk.ch11;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * Filter which handles application authentication.  The filter implements
 * the following policy:
 * <ol>
 * <li>If the username is in the session the filter exits;
 * <li>If not, the authentication cookies are looked for;
 * <li>If found, the authentication is attempted
 * <li>If authentication is successful, the username is stored in 
 * the session
 * <li>Otherwise, the cookies are invalid and subsequently removed 
 * from the response
 * </ol>
 * 
 * @author Bill Siggelkow
 */
public class AutomaticLoginFilter implements Filter {

    private String onFailure = "logon.jsp";

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        onFailure = filterConfig.getInitParameter("onFailure");
    }
   
    public void doFilter(ServletRequest request, ServletResponse response, 
                         FilterChain chain) 
                 throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        
        String contextPath = req.getContextPath( );
        // if the requested page is the onFailure page continue
        // down the chain to avoid an infinite redirect loop        
        if (req.getServletPath( ).equals(onFailure)) {
            chain.doFilter(request, response);
            return;
        }
        
        // get the session or create it  
        HttpSession session = req.getSession( ); 
        String username = (String) session.getAttribute("username");
        if (log.isDebugEnabled( )) log.debug("User in session:"+username);

        // if user is null get credentials from cookie; otherwise continue
        if (username == null) {
            boolean authentic = false;
            username = findCookie(req, "StrutsCookbookUsername");
            String password = findCookie(req, "StrutsCookbookPassword");
            if (username != null && password != null) {
                try {
                    if (log.isDebugEnabled( )) log.debug("Checking 
                                                         authentication");
                    // Call your security service here
                    //SecurityService.authenticate(username, password);
                    session.setAttribute("username", username);
                    authentic = true;
                }
                catch (Exception e) {
                    log.error("Unexpected authentication failure.", e);
                    clearCookie(res, "StrutsCookbookUsername");
                    clearCookie(res, "StrutsCookbookPassword");
                }
            }
    
            // if not authentic redirect to the logon page
            if (!authentic) {
                //redirect to the onFailure page, alternatively we could send
                //an HTTP error code such as 403 (Forbidden)
                res.sendRedirect(contextPath+onFailure);
                //abort filter instead of chaining
                return;
            }
        }
        if (log.isDebugEnabled( )) log.debug("Continuing filter chain ...");
        chain.doFilter(request, response);
    }
    
    public void destroy( ) {
        // Nothing necessary
    }

    private String findCookie(HttpServletRequest request, String cookieName)
    {
        Cookie[] cookies = request.getCookies( );
        String value = null;
        if (cookies != null) {
            for (int i=0; i<cookies.length; i++) {
                if (cookies[i].getName( ).equals(cookieName)) {
                    value = cookies[i].getValue( );
                }
            }
        }
        return value;
    }

    private void clearCookie(HttpServletResponse response, String cookieName)
    {
        // the cookie value does not matter
        Cookie cookie = new Cookie(cookieName, "expired");

        // setting maxAge to 0 effectively removes the cookie
        cookie.setMaxAge(0);
        response.addCookie(cookie);
    }

    private FilterConfig filterConfig;
    private static final Log log = LogFactory.getLog(AutomaticLoginFilter.
                                                     class);
}

Discussion

This Solution assumes that cookies for the username and password have been stored in the request; this filter does not store the cookies.

Tip

For that functionality, you need to use an Action such as the one shown in Recipe 11.5.

You map the servlet filter shown in the Solution to any application URLs requiring user authentication. If users aren’t authenticated, they should be redirected to the page specified by the onFailure initialization parameter (defaults to logon.jsp). You describe the filter’s configuration using the filter and filter-mapping elements in the web.xml file, as shown in Example 11-13.

Example 11-13. Filter deployment settings (partial)
<filter>
    <filter-name>AutomaticLoginFilter</filter-name>
    <filter-class>
        com.oreilly.strutsckbk.ch11.AutomaticLoginFilter
    </filter-class>
    <init-param>
        <param-name>onFailure</param-name>
        <param-value>/my_logon.jsp</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>AutomaticLoginFilter</filter-name>
    <url-pattern>/reg/*</url-pattern>  
</filter-mapping>

<filter-mapping>
    <filter-name>AutomaticLoginFilter</filter-name>
    <url-pattern>/admin/menu.do</url-pattern>  
</filter-mapping>

When a request is received, the servlet filter attempts to retrieve specific cookie values for the username and password. If the values aren’t present, control will be redirected to the onFailure page. If the cookies are present, the username and password will be verified using a SecurityService. If authentic, the request is passed to the next filter in the chain, effectively allowing the request to proceed as normal. If not authentic, the cookie values themselves are invalid and not legitimate. The cookies are removed and control is redirected to the onFailure page.

From a developer’s perspective, one of the more interesting aspects of this filter is the following bit of code:

if (req.getServletPath( ).equals(onFailure)) {
    chain.doFilter(request, response);
    return;
}

When this filter was first written (by yours truly), this block was omitted. The filter was mapped to all application URLs (/) and the application was deployed. When an attempt was made to access to any part of the application, a browser message was displayed indicating too many redirects had been attempted. What happened was that when the filter redirected to the logon page, the request was routed back through the filter, essentially creating an HTTP infinite loop. To fix this problem, the code block was added to skip the authentication check if the request path is the same as the onFailure path.

See Also

Recipe 11.10 shows how to use the open source SecurityFilter software for providing similar functionality as the filter presented in this recipe.

Java Servlet Programming by Jason Hunter (O’Reilly) covers Servlet development in-depth, including filters and the cookie-related APIs.

11.8. Limiting Access for Specific URLs by Role

Problem

You need to verify the user is authorized to access selected URLs based on the user’s security role and profile.

Solution

Use a servlet filter such as the one shown in Example 11-14.

Example 11-14. Authorization filter
package com.oreilly.strutsckbk.ch11.ams;

import java.io.IOException;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.struts.Globals;
import org.apache.struts.action.ActionErrors;
import org.apache.struts.action.ActionMessage;

public class AuthorizationFilter implements Filter {
    public void init(FilterConfig filterConfig) throws ServletException {
        String roles = filterConfig.getInitParameter("roles");
        if (roles == null || "".equals(roles)) {
            roleNames = new String[0];
        } else {
            roles.trim( );
           // use the new split method of JDK 1.4
            roleNames = roles.split("\s*,\s*");
        }
        onFailure = filterConfig.getInitParameter("onFailure");
        if (onFailure == null || "".equals(onFailure)) {
            onFailure = "/index.jsp";
        }
    }

    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;

        HttpSession session = req.getSession( );
        User user = (User) session.getAttribute("user");
        ActionErrors errors = new ActionErrors( );
        if (user != null) {
            boolean hasRole = false;
            for (int i = 0; i < roleNames.length; i++) {
                if (user.hasRole(roleNames[i])) {
                    hasRole = true;
                    break;
                }
            }
            if (!hasRole) {
                errors.add(ActionErrors.GLOBAL_MESSAGE, new ActionMessage(
                        "error.authorization.required"));
            }
        }
        if (errors.isEmpty( )) {
            chain.doFilter(request, response);
        } else {
            req.setAttribute(Globals.ERROR_KEY, errors);
            req.getRequestDispatcher(onFailure).forward(req, res);
        }
    }

    public void destroy( ) {
    }

    private String[] roleNames;

    private String onFailure;
}

Discussion

Servlet filters, introduced as part of the Servlet 2.3 specification, provide for custom request and response processing that can be applied across any (and all) web resources. Filters can alter a request before it arrives at its destination and, likewise, can modify the response after it leaves a destination. Filters can be applied to static HTML pages, JSP pages, Struts actions, essentially any resource that you can specify with a URL.

You can use a filter to prohibit or allow access to resources based on any user information. Container-managed security (Recipe 11.9) provides a limited form of this capability, known as role-based access control. With a filter, you can implement role-based access control or any other security policy desired. The Solution shows an example usage that implements custom role-based authorization. This filter performs a similar security check as the custom RoleRequestProcessor of Example 11-14.

Initialization parameters specify the required roles and the page to forward to if the authorization check fails. For each combination of roles and mapped URLs, you can deploy a separate instance of the filter class. Each instance, specified by the filter element, can have its own set of initialization parameters and filter mappings. Example 11-15, taken from the web.xml file, shows a deployment that uses two instances of the same authorization filter.

Example 11-15. Deploying two instances of the authorization filter (partial)
<filter>
    <filter-name>adminAuthFilter</filter-name>
    <filter-class>
        com.oreilly.strutsckbk.ch11.ams.AuthorizationFilter
    </filter-class>
    <init-param>
        <param-name>roles</param-name>
        <param-value>admin</param-value>
    </init-param>
    <init-param>
        <param-name>onFailure</param-name>
        <param-value>/index.jsp</param-value>
    </init-param>
</filter>

<filter>
    <filter-name>managerAuthFilter</filter-name>
    <filter-class>
        com.oreilly.strutsckbk.ch11.ams.AuthorizationFilter
    </filter-class>
    <init-param>
        <param-name>roles</param-name>
        <param-value>manager,asstManager</param-value>
    </init-param>
    <init-param>
        <param-name>onFailure</param-name>
        <param-value>/index.jsp</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>adminAuthFilter</filter-name>
    <url-pattern>/admin/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name> managerAuthFilter </filter-name>
    <url-pattern>/mgr/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name> managerAuthFilter </filter-name>
    <url-pattern>/usr/*</url-pattern>
</filter-mapping>

Unlike other filters in this chapter, the authorization filter of Example 11-14 truly integrates with Struts. If a user doesn’t have a required role, then a set of ActionErrors are created and stored as a request attribute. Though the filter only has access to the request and response, it integrates with Struts based on knowing how Struts stores the ActionErrors in the request. Unlike Actions, filters don’t have access to Struts helper methods for accessing objects, like ActionErrors, that may be stored in the servlet request or session. To get around this, you can retrieve Struts objects. By using the constants defined in the org.apache.struts.Globals class, you can protect your code from future changes to Struts internals.

See Also

For a complete application-managed security solution, you will want to implement an authentication mechanism. Recipe 11.6 presents a servlet filter that can be used for this purpose. To enforce authentication prior to authorization, list the authentication filter before the authorization filter in the filter-mapping declarations.

Recipe 11.10 shows how to use the open source SecurityFilter software for providing similar functionality as the filter presented in this recipe.

Java Servlet Programming by Jason Hunter (O’Reilly) covers servlet filters in-depth. Sun’s Java site has a good article on the essentials of servlet filters. It can be found at http://java.sun.com/products/servlet/Filters.html.

11.9. Letting the Container Manage Security

Problem

You want to let the container manage security for your Struts application instead of you having to write all the Java code to support log in (authentication) and access checks (authorization).

Solution

Use container-managed security, as defined by the Java Servlet Specification.

Discussion

A servlet container or J2EE application server can manage security for web applications. Container-managed security provides three main features:

Authentication

You can specify to the container how users are to be authenticated using a login configuration. You indicate if you want the browser to prompt for the username and password, or if you want to use your own custom login page.

Authorization

You can establish security constraints that allow users with certain roles access to specific URLs of the application. If users attempt to access a page to which they aren’t authorized, they will be prompted to login using the login configuration.

Secure transport

You can specify which URLs should be accessed using a secure protocol. In practical terms, you indicate which pages can be accessed with the HTTPS protocol (HTTP over Secure Socket Layer).

You configure container-managed security using special XML elements in your web.xml, as shown in Example 11-16.

Example 11-16. Configuring container-managed security in web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Struts Cookbook - Chapter 11 : CMS</display-name>

    <!-- Action Servlet Configuration -->
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Action Servlet Mapping -->
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <!-- The Welcome File List -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

    <!-- Container-managed security configuration -->
    <security-constraint>
            <!-- At least one web-resource collection -->
        <web-resource-collection>
          <web-resource-name>RegPages</web-resource-name>
          <description>Registered user pages</description>
          <url-pattern>/reg/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <!-- Zero or more role-names --> 
            <role-name>jscUser</role-name>
        </auth-constraint>
    </security-constraint>

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>AdminPages</web-resource-name>
            <description>Administrative pages</description>
            <url-pattern>/admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>jscAdmin</role-name>
        </auth-constraint>
        <!-- Switch to HTTPS for the admin pages -->
        <user-data-constraint>
            <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>StrutsCookbookCh11</realm-name>
        <form-login-config>
            <form-login-page>/cma_logon.jsp</form-login-page>
            <form-error-page>/cma_logon_error.jsp</form-error-page>
        </form-login-config>
    </login-config>

    <security-role>
        <description>Registered User</description>
        <role-name>jscUser</role-name>
    </security-role>

    <security-role>
        <description>Administrators</description>
        <role-name>jscAdmin</role-name>
    </security-role>

</web-app>

Authentication and authorization

You use the security-constraint element to apply constraints to one or more web resource collections—i.e., a set of URLs. URL patterns (see the Sidebar 11-1) identify the URLs that comprise each collection. The auth-constraint element identifies the user roles, specified using role-name elements, which can access a constrained URL. If users attempt to access a constrained URL, they must log in based on settings in the login-config element.

The login-config element indicates the authentication to be performed and where the user information can be found. A web application can have one login configuration. The auth-method nested element indicates the type of authentication and accepts the values detailed in Table 11-1.

Table 11-1. J2EE login configuration types

Authentication method

Description

BASIC

The browser pops up a dialog allowing the user to enter a username and password. The username and password are Base-64 encoded and sent to the server.

FORM

Allows for a custom form to be specified. The form must contain a j_username field for the username and j_password field for the password. The form must submit to j_security_check. The username and password are Base-64 encoded.

DIGEST

Just like BASIC authentication, except that the username and password are encrypted into a message digest value. All browsers may not support this configuration.

CLIENT-CERT

The client is required to provide a digital certificate for authentication. This is the most secure configuration and is the most costly; certificates for production use must be purchased from a Certificate Authority.

The majority of applications employing container-managed security use BASIC or FORM-based authentication. With FORM-based authentication, the form-login-page element specifies an HTML or JSP page that users use to submit their authentication credentials. That page, like the one shown in Example 11-17, must submit to the form action named j_security_check and have form fields named j_username and j_password.

Example 11-17. Form-based authentication login page
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>

<html>
<head>
<title><bean:message key="logon.cma.title"/></title>
</head>

<body>
<form action="j_security_check">
    <table border="0" width="100%">
        <tr>
            <th align="right">
                <bean:message key="prompt.username"/>:
            </th>
            <td align="left">
                <input type="text" name="j_username" size="16" maxlength="18">
            </td>
        </tr>

        <tr>
            <th align="right">
                <bean:message key="prompt.password"/>:
            </th>
            <td align="left">
                <input type="password" name="j_password" size="16" 
                maxlength="18">
            </td>
        </tr>

        <tr>
            <td align="right">
                <input type="submit" value="Submit">
            </td>
            <td align="left">
                <input type="reset">
            </td>
        </tr>
    </table>
</form>
</body>
</html>

In addition to specifying the login, the login configuration may also specify a security realm. A security realm is essentially the store from which a web application retrieves and verifies user credentials. In addition, a realm provides a mechanism for specifying the roles users may have.

The user data for authentication comes from the security realm. The security realm serves as a reference to container-specific security storage. Realms can be based, for example, on property files, XML files, a relational database, or an LDAP-server. Some containers, such as JBoss, provide a mapping between a realm and a Java Authentication and Authorization Service (JAAS) implementation. The mechanism for associating the logical realm named in the web.xml to the concrete realm varies by container.

If you are experimenting with container-managed security and using Tomcat, you can use Tomcat’s UserDatabase realm. In a default configuration, Tomcat supports this realm across all deployed applications. The realm uses usernames, passwords, roles, and role assignments specified in the conf/tomcat-users.xml file. A sample file is shown in Example 11-18.

Example 11-18. Sample Tomcat users file
<?xml version='1.0' encoding='utf-8'?>
<tomcat-users>
  <role rolename="jscUser"/>
  <role rolename="tomcat"/>
  <role rolename="role1"/>
  <role rolename="manager"/>
  <role rolename="jscAdmin"/>
  <role rolename="admin"/>
  <user username="bsiggelkow" password="crazybill" roles="jscUser,jscAdmin"/>
  <user username="tomcat" password="tomcat" roles="tomcat"/>
  <user username="role1" password="tomcat" roles="role1"/>
  <user username="both" password="tomcat" roles="tomcat,role1"/>
  <user username="gpburdell" password="gotech" roles="jscUser"/>
  <user username="admin" password="admin" roles="admin,manager"/>
</tomcat-users>

When users attempt to access an authorization-constrained URL, they will be challenged to enter authentication credentials. If you are using FORM-based authentication, the form-login-page will be displayed. Once the user has been authenticated, your web application can glean useful user data from the HTTP request. The HttpServletRequest provides three particular methods enabled when using container-managed security: getUserPrincipal( ); getRemoteUser( ), which returns user identity information, such as the username; and isUserInRole(), which determines if a user has a specified role. These methods can be used in your Action classes to perform such things as the following:

  • Loading the user’s profile and storing it in the session

  • Rendering a specific response or redirect to a certain URL based on the user’s role

  • Allowing role-based access to Actions as configured in the struts-config.xml file

  • Hiding or displaying presentation components (links, buttons, menus, etc.) based on a user’s role (using the logic:present and logic:notPresent tags)

A drawback to the challenge/response authentication model of container-managed security is that users must attempt access to a constrained URL to log in. This behavior can make it difficult for a user to log in proactively. A common trick to permit proactive logins is to create a link on an unsecured page to an authorization-constrained JSP page. Because the JSP page is secured, the user will be forced to log in. The secured JSP page then redirects back to the original unsecured page using the logic:redirect Struts tag:

<%@ taglib uri="http://struts.apache.org/tags-logic.tld" prefix="logic" %>
<logic:redirect page="/index.jsp"/>

Many web applications place the login form on every publicly accessible page, usually near the top of the page on the left or right. With container-managed security, however, this technique can’t easily be employed. The container itself can only reference the login form specified in the login-config. This limitation forces the chicanery required to emulate a proactive login.

There is no easy workaround for this problem. If you need this capability, as many applications do, use application-managed security. To get the best of both worlds, consider using the Solution shown in Recipe 11.10.

Secure transport

Container-managed security allows you to force portions of your applications to run under the Secure Socket Layer (SSL) transport, using HTTP over SSL (HTTPS). The example web.xml file (Example 11-16) configures the AdminPages to effectively run under the HTTPS:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>AdminPages</web-resource-name>
        <description>Administrative pages</description>
        <url-pattern>/admin/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>jscAdmin</role-name>
    </auth-constraint>
    <!-- Switch to HTTPS for the admin pages -->
                  <user-data-constraint>
                  <transport-guarantee>CONFIDENTIAL</transport-guarantee>
                  </user-data-constraint>
</security-constraint>

The transport-guarantee element accepts values of NONE, INTEGRAL, and CONFIDENTIAL. Specifying either of the latter two values requires requests to the URLs to use the HTTPS protocol over a secured port (typically port 443 or 8443). The value of NONE indicates no particular transport security is required.

Specifying a transport-guarantee of NONE won’t make the container switch from the secured protocol (https) to the unsecured protocol (http). Unless you specify http on a request, the application will continue to use https. If you need to switch protocols, consider using the Solution shown in Recipe 11.11.

Most application servers and servlet containers accept the HTTPS protocol. However, you may need to configure the container.

See Also

Container-managed security is convenient and easy to configure, but it can make your application inflexible to changing security requirements and less portable between application servers. Recipe 11-10 provides a Solution that mitigates these problems.

Enabling a servlet container to support https varies by application server. Tomcat provides a simple how-to for this. For Tomcat 5.0, the relevant documentation can be found at http://jakarta.apache.org/tomcat/tomcat-5.0-doc/ssl-howto.html.

If you need finer-grained control of transport security than can be provided by container-managed security, consider using the Solution shown in Recipe 11.11.

11.10. Mixing Application-Managed and Container-Managed Security

Problem

You want the convenience of container-managed security, yet need a custom mechanism for implementing your security policies.

Solution

Use the SecurityFilter (http://securityfilter.sourceforge.net) custom servlet filter and associated classes.

Discussion

Container-managed security, as shown in Recipe 11.9, has some advantages:

  • When users attempt to access a protected URL, the container automatically prompts them to logon. Once authenticated, they are forwarded to the originally requested URL.

  • The user identity can be determined using the getUserPrincipal( ) or getRemoteUser( ) methods of the HttpServletRequest. These methods can determine if a user is logged in.

  • You can determine if a user has a specific role using the isUserInRole( roleName ) method of the HttpServletRequest. Struts leverages this feature to provide role-constrained actions via the roles attribute. Struts provides for role-specific page generation using the logic:present role="roleNames" custom JSP tag.

Container-managed security has drawbacks, such as portability. With container-managed security, the implementation is split between your web application and the application server. You usually must configure container-specific resources to specify the repository, known as a security realm, from which the container acquires the user’s credentials and roles. Container-managed security will only prompt users to login if they attempt access of a protected URL. Users cannot log in by going to a known page and entering their username and password. This restriction makes it difficult, for example, to include a login form on every page.

The SecurityFilter servlet filter and related classes provide a hybrid of container-managed security and application-managed security that solves most of these problems. SecurityFilter permits implementation of a custom security policy yet allows programmatic access to user identity and role information via the standard HttpServletRequest methods. You configure the SecurityFilter through an XML file. The format of this file is near identical to the security-constraint elements used for container-managed security in the web.xml. Example 11-19 shows a sample securityfilter-config.xml file. This example is similar to the web.xml for container-managed security shown in Recipe 11.9.

Example 11-19. Configuration for SecurityFilter
<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE securityfilter-config PUBLIC
    "-//SecurityFilter.org//DTD Security Filter Configuration 2.0//EN"
    "http://www.securityfilter.org/dtd/securityfilter-config_2_0.dtd">

<securityfilter-config>

   <security-constraint>
        <web-resource-collection>
          <web-resource-name>RegPages</web-resource-name>
          <description>Registered user pages</description>
          <url-pattern>/reg/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>jscUser</role-name>
        </auth-constraint>
   </security-constraint>

   <security-constraint>
      <web-resource-collection>
         <web-resource-name>AdminPages</web-resource-name>
         <url-pattern>/admin/*</url-pattern>
      </web-resource-collection>
      <auth-constraint>
         <role-name>jscAdmin</role-name>
      </auth-constraint>
   </security-constraint>

    <!-- Use this login-config to test BASIC authentication -->
    <!--
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>StrutsCookbookCh11</realm-name>
    </login-config>
    -->
    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>StrutsCookbookCh11</realm-name>
        <form-login-config>
            <form-login-page>/sf_logon.jsp</form-login-page>
            <form-error-page>/sf_logon_error.jsp</form-error-page>
            <form-default-page>/Welcome.do</form-default-page>
        </form-login-config>
    </login-config>

    <security-role>
        <description>Regular Users</description>
        <role-name>jscUser</role-name>
    </security-role>

    <security-role>
        <description>Administrators</description>
        <role-name>jscAdmin</role-name>
    </security-role>

    <realm className="com.oreilly.strutsckbk.ch11.sf.MemorySecurityRealm"/>

</securityfilter-config>

Like container-managed security, with SecurityFilter you can specify allowed roles for URLs using the security-constraint element. SecurityFilter supports BASIC and FORM-based authentication. Unlike container-managed security, SecurityFilter allows for a user to perform an “unsolicited” login. That is, the user can log in without having to attempt access to a protected URL. In this scenario, once logged in, the user will be forwarded to the page specified in the form-default-page element. The logon page that you specify for the form-login-page element follows the same convention as container-managed security. The logon form, sf_logon.jsp, shown in Example 11-20, submits the j_username and j_password fields to j_security_check.

Example 11-20. SecurityFilter logon page
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://struts.apache.org/tags-bean" prefix="bean" %>

<html>
<head>
<title>Security Filter : Logon Page</title>
</head>

<body>
<form method="POST" action="j_security_check">
    <table border="0" width="100%">
        <tr>
            <th align="right">
                <bean:message key="prompt.username"/>:
            </th>
            <td align="left">
                <input type="text" name="j_username" size="16" 
                maxlength="18">
            </td>
        </tr>

        <tr>
            <th align="right">
                <bean:message key="prompt.password"/>:
            </th>
            <td align="left">
                <input type="password" name="j_password" size="16" 
                maxlength="18">
            </td>
        </tr>

        <tr>
            <td align="right">
                <input type="submit" value="Submit">
            </td>
            <td align="left">
                <input type="reset">
            </td>
        </tr>
    </table>
</form>
</body>
</html>

With container-managed security, you have to separate the security realm configuration and code from the rest of the web application. The configuration is usually part of a container-specific XML file, and the code usually needs to be placed in a separate JAR file or in the server’s classpath. With SecurityFilter, however, you include the configuration and code for your realm with your web application. It all gets bundled together in the same WAR file. Your custom realm must implement the SecurityRealmInterface interface (shown in Example 11-21). SecurityFilter is licensed under the SecurityFilter Software License, derived from and compatible with the Apache Software License. (For brevity, the license has been excluded in this example.)

Example 11-21. SecurityFilter realm interface
package org.securityfilter.realm;
import java.security.Principal;

public interface SecurityRealmInterface {

   /**
    * Authenticate a user.
    *
    * @param username a username
    * @param password a plain text password, as entered by the user
    *
    * @return a Principal object representing the user if successful, 
    * false otherwise
    */
   public Principal authenticate(String username, String password);

   /**
    * Test for role membership.
    *
    * Use Principal.getName( ) to get the username from the principal object.
    *
    * @param principal Principal object representing a user
    * @param rolename name of a role to test for membership
    *
    * @return true if the user is in the role, false otherwise
    */
   public boolean isUserInRole(Principal principal, String rolename);
}

If you want to work with only usernames and passwords and don’t need to create Principals, you can extend the SimpleSecurityRealmBase class. The custom MemorySecurityRealm, shown in Example 11-22, extends this class and implements the booleanAuthenticate( ) and isUserInRole() methods by delegating to a custom security service.

Example 11-22. Extending the SimpleSecurityRealmBase
package com.oreilly.strutsckbk.ch11.sf;

import org.securityfilter.realm.SimpleSecurityRealmBase;

public class MemorySecurityRealm extends SimpleSecurityRealmBase {

   private SecurityService serviceImpl = new SecurityServiceImpl( );

   public boolean booleanAuthenticate(String username, String password) {
       try {
           User user = serviceImpl.authenticate(username, password);
           if (user != null) return true;
       } catch (SecurityException e) {
           e.printStackTrace( );
       }
       return false;
   }

   public boolean isUserInRole(String username, String role) {
       User user = serviceImpl.findUser(username);
       return user == null ? false : user.hasRole(role);
   }
}

The realm is configured and deployed in the securityfilter-config.xml file:

<realm className="com.oreilly.strutsckbk.ch11.sf.MemorySecurityRealm"/>

Optionally, you can declaratively set properties on a custom realm using the realm-param element:

<realm className="fully.qualified.classname.of.SecurityRealm">
    <realm-param name="propertyName" value="propertyValue" />
</realm>

You describe the deployment of the actual SecurityFilter servlet filter in the web.xml file as shown in Example 11-23.

Example 11-23. Declaring SecurityFilter in the deployment descriptor
<?xml version="1.0" encoding="ISO-8859-1"?>

<!DOCTYPE web-app PUBLIC
    "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>

    <display-name>Struts Cookbook - Chapter 11 : SecurityFilter</display-name>

    <!-- Security Filter -->
    <filter>
        <filter-name>Security Filter</filter-name>
        <filter-class>org.securityfilter.filter.SecurityFilter</filter-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/securityfilter-config.xml</param-value>
            <description>
              Configuration file location (this is the default value)
            </description>
       </init-param>
       <init-param>
            <param-name>validate</param-name>
            <param-value>true</param-value>
            <description>Validate config file if set to true</description>
       </init-param>
    </filter>

    <!-- map all requests to the SecurityFilter -->
    <filter-mapping>
        <filter-name>Security Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Action Servlet Configuration -->
    <servlet>
        <servlet-name>action</servlet-name>
        <servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
        <init-param>
            <param-name>config</param-name>
            <param-value>/WEB-INF/struts-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Action Servlet Mapping -->
    <servlet-mapping>
        <servlet-name>action</servlet-name>
        <url-pattern>*.do</url-pattern>
    </servlet-mapping>

    <!-- The Welcome File List -->
    <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
    </welcome-file-list>

</web-app>

It’s best to map all requests to the filter. Let the securityfilter-config.xml file set the security constraints for specific URLs.

SecurityFilter provides support for automatic logins using cookies. This gives you a similar capability to the custom solution shown in Recipe 11.7. Furthermore, you can configure all the details about the cookies, such as expiration and encryption, in the securityfilter-config.xml file, as shown in Example 11-24.

Example 11-24. Configuring the “remember me” cookie
<form-login-config>
    <!--Logon page must contain a checkbox for j_rememberme -->
    <form-login-page>/sf_logon.jsp</form-login-page>
    <form-error-page>/sf_logon_error.jsp</form-error-page>
    <form-default-page>/Welcome.do</form-default-page>
    <!-- remember-me config -->
    <remember-me
className="org.securityfilter.authenticator.persistent.
           DefaultPersistentLoginManager">
        <!-- optional settings for default persistent login manager -->
        <remember-me-param name="cookieLife" value="15"/>
        <remember-me-param name="protection" value="all"/>
        <remember-me-param name="useIP" value="true"/>
        <remember-me-param name="encryptionAlgorithm" value="DES"/>
        <remember-me-param name="encryptionMode" value="ECB"/>
        <remember-me-param name="encryptionPadding" value="PKCS5Padding"/>
        <!-- encryption keys; customize for each application -->
        <!-- NOTE: these kys must be speciied AFTER other 
        encryption settings -->
        <remember-me-param name="validationKey" 
value="347382902489402489754895734890347"/>
        <remember-me-param name="encryptionKey" 
value="347892347028490237487846240673842"/>
    </remember-me>
</form-login-config>

You enable the “remember me” capability by adding a checkbox to your logon form with the name of j_rememberme. Here’s what you would add to the logon page shown in Example 11-20:

<tr>
    <th align="right">Remember me:</th>
    <td align="left">
        <input type="checkbox" name="j_rememberme" value="true">
    </td>
</tr>

The behavior you get with the SecurityFilter will look and feel like container-managed security. If users attempt to access a protected page, they will be prompted to log in. Of course, if they use the “remember me” feature, they can automatically log in. They can log in without having to attempt access to a protected page. Once authenticated, control is forwarded to the form-default-page.

Using SecurityFilter, you can use the getUserPrincipal( ), getRemoteUser( ), and isUserInRole( ) methods of the HttpServletRequest as if you were using full-blown container-managed security. Struts support for roles—the roles attribute of the action element and the roles attribute of the logic:present tag—will work as intended.

See Also

The SecurityFilter project’s home page can be found at http://securityfilter.sourceforge.net. SecurityFilter provides a framework for authentication and authorization using a servlet filter. To understand how filters work for these purposes, take a look at Recipes Section 11.6 and Section 11.8.

Recipe 11.9 discusses the use of container-managed security.

11.11. Configuring Actions to Require SSL

Problem

You want to control if HTTPS is required on a page-by-page basis.

Solution

Use the SSLEXT Struts extension.

Discussion

The Struts SSL Extension (SSLEXT), an open source Struts plug-in, enables you to indicate if an action requires the secure (https) protocol. Steve Ditlinger created and maintains this project (with others), hosted at http://sslext.sourceforge.net.

SSLEXT enables fine-grained secure protocol control by providing:

  • The ability to specify in the struts-config.xml file if an action should require a secure protocol. This feature essentially allows your application to switch actions and JSP pages from http to https.

  • Extensions of the Struts JSP tags that generate URLs that include the https protocol.

The SSLEXT distribution consists of a plug-in class for initialization (SecurePlugIn), a custom request processor (SecureRequestProcessor), and a custom action mapping class (SecureActionMapping).

Warning

If you have been using custom RequestProcessor or ActionMapping classes and you want to use SSLEXT, you will need to change these classes to extend the corresponding classes provided by SSLEXT.

For JSP pages, SSLEXT provides custom extensions of Struts tags for generating protocol-specific URLs. A custom JSP allows you to indicate if a JSP page requires https. SSLEXT depends on the Java Secure Socket Extension (JSSE). JSSE is included with JDK 1.4 or later. If you’re using an older JDK, you can download JSSE from Sun’s Java site. Finally, you’ll need to enable SSL for your application server. For Tomcat, this can be found in the Tomcat SSL How-To documentation.

SSLEXT works by intercepting the request in its SecureRequestProcessor. If the request is directed toward an action that is marked as secure, the SecureRequestProcessor will generate a redirect. The redirect will change the protocol to https and the port to a secure port (e.g., 443 or 8443). Switching protocols sounds simple; however, a request in a Struts application usually contains request attributes, and these attributes are lost on a redirect. SSLEXT solves this problem by temporarily storing the request attributes in the session.

You can download the SSLEXT distribution from the project web site. SSLEXT doesn’t include a lot of documentation, but it comes with sample applications that demonstrate its use and features. If all your requests go through Struts actions, you can apply SSLEXT without modifying any Java code or JSP pages. Here’s how you would apply SSLEXT to a Struts application:

  1. Copy the sslext.jar file into your application’s WEB-INF/lib folder.

  2. If you need to use the custom JSP tags, copy the sslext.tld file into the WEB-INF/lib folder.

Make the following changes to the struts-config.xml file:

  1. Add the type attribute to the action-mappings element to specify the custom secure action mapping class:

    <action-mappings type="org.apache.struts.config.SecureActionConfig">
  2. Add the controller element for the secure request processor:

    <controller processorClass="org.apache.struts.action.SecureRequestProcessor" />
  3. Add the plug-in declaration to load the SSLEXT code:

    <plug-in className="org.apache.struts.action.SecurePlugIn">
        <set-property property="httpPort" value="80"/>
        <set-property property="httpsPort" value="443"/>      
        <set-property property="enable" value="true"/>      
        <set-property property="addSession" value="true"/>      
    </plug-in>
  4. Set the secure property to true for any action you want to be accessed using https:

    <action    path="/reg/Main"
               type="com.oreilly.strutsckbk.ch11.ssl.MainMenuAction">
        <!-- Force this action to run secured -->
        <set-property property="secure" value="true"/>
        <forward name="success" path="/reg/main.jsp"/>
    </action>
  5. Set the secure property to false for any action that you only want to run under an unsecured protocol (http):

    <action    path="/Welcome"
               type="com.oreilly.strutsckbk.ch11.ssl.WelcomeAction">
        <!-- Force this action to run unsecured -->
        <set-property property="secure" value="false"/>
        <forward name="success" path="/welcome.jsp"/>
    </action>

If you have accessible JSP pages you want to specify as secured (or unsecured), use the SSLEXT pageScheme custom JSP tag:

<%@ taglib uri="http://www.ebuilt.com/taglib" prefix="sslext"%>
<sslext:pageScheme secure="true"/>

Now rebuild and deploy the application. When you click on a link to a secured action, the protocol will switch to https and the port to the secure port (e.g., 8443 or 443 ). If you go to an action marked as unsecured, the protocol and port should switch back to http and the port to the standard port (e.g., 8080 or 80). If you access an action without a specified value for the secure property or the value is set to any, then the protocol won’t switch when you access the action. If you’re under http, the protocol will remain http; if you’re under https, the protocol will remain https.

Warning

Be careful if you switch from a secured to unsecured protocol (https to http). Critical user-specific data, such as the current session ID, can be snooped by a hacker. The hacker could use this data to hijack the session and imposter the user. Here is a good rule to follow: Once you switch to https, stay in https.

You can use SSLEXT alongside container-managed security mechanisms for specifying secure transport. The container-managed security approach works well when you want to secure entire portions of your application:

<security-constraint>
    <web-resource-collection>
        <web-resource-name>AdminPages</web-resource-name>
        <description>Administrative pages</description>
        <url-pattern>/admin/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>jscAdmin</role-name>
    </auth-constraint>
    <!-- Switch to HTTPS for the admin pages -->
               <user-data-constraint>
               <transport-guarantee>CONFIDENTIAL</transport-guarantee>
               </user-data-constraint>
</security-constraint>

You can then use SSLEXT for fine-grained control of the protocol at the action level.

See Also

Enabling an application server to support https varies. Tomcat provides a how-to for this. For Tomcat 5.0, the relevant documentation can be found at http://jakarta.apache.org/tomcat/tomcat-5.0-doc/ssl-howto.html.

SSLEXT is hosted on SourceForge at http://sslext.sourceforge.net.

Craig McClanahan presents a good argument against switching back to http from https. His comments can be found in a struts-user mailing list thread archived at http://www.mail-archive.com/[email protected]/msg81889.html.

Recipe 11.9 shows how you can specify the protocol in the web.xml file. This approach, presented as part of the J2EE tutorial, can be found at http://java.sun.com/j2ee/1.4/docs/tutorial/doc/Security4.html.

11.12. Limiting the Size of Uploaded Files

Problem

You want to limit the size of a file to be uploaded to your application.

Solution

In the struts-config.xml file, set the maxFileSize attribute on the controller element to the maximum accepted size (in bytes) for an uploaded file. In this example, a single uploaded file must be smaller than 700 KB:

<controller maxFileSize="700K"/>

Discussion

Whether intended or not, users may attempt to upload excessively large files to your web application. In most cases, the user accidentally picked the wrong file; however, a malicious user could be attempting to bring down your application. You can restrict uploads to a maximum file size using the maxFileSize attribute on the controller element in your struts-config.xml file. The value for this attribute is expressed as an integer value optionally followed by a “K,” “M,” or “G,” interpreted as kilobytes, megabytes or gigabytes, respectively. If you specify the integer valuewith no units indicated, the value will be interpreted as bytes.

If you attempt to upload a file larger than the acceptable maximum, the FormFile property of the ActionForm will be null. You can handle this condition in your Action that processes the upload, as shown in Example 11-25.

Example 11-25. Handling null FormFile property (partial)
public ActionForward execute(ActionMapping mapping,
                             ActionForm form,
                             HttpServletRequest request,
                             HttpServletResponse response) throws Exception {

    // Get the form file property from the form
    UploadForm uploadForm = (UploadForm) form;        
    FormFile content = uploadForm.getContent( );

    if (content == null) {
        ActionMessage msg = new ActionMessage("error.maxFileSize.exceeded");
        ActionMessages errors = new ActionMessages( );
        errors.add(ActionMessages.GLOBAL_MESSAGE, msg);
        saveErrors(request, errors);
        return mapping.getInputForward( );
     }

     // continue processing upload ...

If you don’t specify the maxFileSize attribute, the default maximum will be 250 MB (250M). If you want a size different size than this, you must specify it (as shown in the Solution).

The controller element supports a related attribute with the name of memFileSize. This attribute specifies the maximum amount of memory that will be used to hold an uploaded file. If a file is larger than this amount, it will be written to some external storage, typically the filesystem. The value for the attribute is specified using the same notation as the maxFileSize attribute. The default memory file size is 256 KB. Here, the maximum file size is set to 5 MB, and the maximum amount held in memory is set to 500 KB:

<controller maxFileSize="5M" memFileSize="500K"/>

The memFileSize property sets the size threshold which determines at what point an uploaded file will be written to disk or cached in memory. The default value for this setting is 10,240 bytes (10K). Some containers are configured to allow limited or no ability for writing to disk from within a web application. If your container has this restriction, you may need to adjust this setting to a value greater than the largest expected file size.

See Also

Recipe 7.10 shows you how to allow users to upload files to your application.

The Struts User’s Guide discusses controller configuration. The relevant section can be found at http://struts.apache.org/userGuide/configuration.html#controller_config.

Beneath the covers, Struts use the Jakarta Commons FileUpload package. Complete documentation and source for this package can be found at http://jakarta.apache.org/commons/fileupload.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.93.132