CSC/ECE 517 Fall 2009/wiki2 3 b5

From Expertiza_Wiki
Jump to navigation Jump to search

Synchronizer Token Pattern

Problem summary

Many websites rely on synchronous activity between the client and server. This synchronization can be disrupted if the client takes actions in an order not expected by the server. These types of actions could produce unpredictable results and must be protected against.

For instance, when submitting a purchase in an online store, the client might click the "Purchase" button multiple times. They might hit their browser's back button and take another action while the transaction is still processing. In this example it might result in the client's account being charged multiple times, or multiple orders being shipped which aren't charged, etc.

A client could also click their back button after submitting the form, or refresh the page. In addition to the problems caused by multiple clicks, this could also cause an attempt to access data that no longer exists (e.g. credit card number purged from system after transaction completed). Depending on how robust the system is, this could cause other unpredictable results.

Finally, the client might have malicious intent and be trying to subvert the site. By exploiting desynchronization between the client and server the client may be able to perform actions they should not. A good example is an online poll. By clicking multiple times on a vote button very quickly before the vote is processed, the client may be able to get multiple votes in before the system realizes they are stuffing the ballot box.

Overview

The Synchronizer Token pattern is a server side solution to this problem. The concept is fairly simple: Establish a token on the server side that indicates a valid submission, and give a token signature to the client that corresponds to that token (most likely in a hidden input field). When the client submits their form, the server validates their token and proceeds. It then marks the token as invalid so it may not be used again. The result is that any given form may only be used once and then will not work again.

The control flow diagram below depicts the synchronizer token pattern working with a valid token (normal usage):

The control flow diagram below depicts the synchronizer token pattern working with an invalid token (erroneous usage):

Example application

Java (with Struts)

The key methods in this implementation are saveToken(HttpServletRequest req), isTokenValid(HttpServletRequest req), and reset(HttpServletRequest req) which create, validate, and destroy tokens, respectively. resetToken(HttpServletRequest req) may also be used to destroy the token.

From Java World:

public final ActionForward perform(ActionMapping mapping,
        ActionForm form,
        HttpServletRequest request,
        HttpServletResponse response)
    throws IOException, ServletException {
    HttpSession session = request.getSession();
    ActionForward forward = null;
    if (isTokenValid(request)) {
        // Reset token and session attributes
        reset(request);
        try {
            // Perform the action and store the results
            forward = performSynchro(mapping, form, request, response);
            session.setAttribute(FORM_KEY, form);
            session.setAttribute(FORWARD_KEY, forward);
            ActionErrors errors = (ActionErrors) 
                        request.getAttribute(Action.ERROR_KEY);
            if (errors != null && !errors.empty()) {
                saveToken(request);
            }
            session.setAttribute(ERRORS_KEY, errors);
            session.setAttribute(COMPLETE_KEY, "true");
        } catch (IOException e) {
            // Store and rethrow the exception
            session.setAttribute(EXCEPTION_KEY, e);
            session.setAttribute(COMPLETE_KEY, "true");
            throw e;
        } catch (ServletException e) {
            // Store and rethrow the exception
            session.setAttribute(EXCEPTION_KEY, e);
            session.setAttribute(COMPLETE_KEY, "true");
            throw e;
        }
    } else {
        // If the action is complete
        if ("true".equals(session.getAttribute(COMPLETE_KEY))) {
            // Obtain the exception from the session
            Exception e = (Exception) session.getAttribute(EXCEPTION_KEY);
            // If it is not null, throw it
            if (e != null) {
                if (e instanceof IOException) {
                    throw (IOException) e;
                } else if (e instanceof ServletException) {
                    throw (ServletException) e;
                }
            }
            // Obtain the form from the session
            ActionForm f = (ActionForm) session.getAttribute(FORM_KEY);
            // Set it in the appropriate context
            if ("request".equals(mapping.getScope())) {
                request.setAttribute(mapping.getAttribute(), f);
            } else {
                session.setAttribute(mapping.getAttribute(), f);
            }
            // Obtain and save the errors from the session
            saveErrors(request, (ActionErrors)
                    session.getAttribute(ERRORS_KEY));
            // Obtain the forward from the session
            forward = (ActionForward) session.getAttribute(FORWARD_KEY);
        } else {
            // Perform the appropriate action in case of token error
            forward = performInvalidToken(mapping, form, request, response);
        }
    }
    return forward;
}

Ruby on Rails

A synchronizer token pattern could be easily implemented in Ruby on Rails by using before_filter and after_filter to perform token creation and invalidation at the appropriate times (create token before displaying the form page, invalidate token after taking the submit action).

Critique

Use of this pattern requires the programmer to consider another alternative flow for if the token is invalid. Depending on the design decisions made this may complicate the program. For example, if the user tries to submit a large amount of form data with an invalid token, the designer might want to preserve their entries while issuing a new token. Failure to preserve entries might cause a user to give up and not follow through with their action on the site. However, this is an additional feature that must then be designed, added, and tested.

Other solutions

The common alternative to the synchronizer token pattern is to simply use Javascript or similar to disable the "submit" button on the client side. This solves the issue of multiple clicks on the button without the need for an alternative program flow. However, this is less reliable since the user might still use their back button or a bookmark to revisit the page.

Resources

Java World Tip 136
Core J2EE Patterns