Yes, tokens are used to prevent duplicate submits of a single form.
These are the four methods your Action inherits from org.apache.struts.action.Action:
protected String generateToken(HttpServletRequest request) { return token.generateToken(request); }
protected boolean isTokenValid(HttpServletRequest request) { return token.isTokenValid(request, false); }
protected void resetToken(HttpServletRequest request) { token.resetToken(request); }
protected void saveToken(HttpServletRequest request) { token.saveToken(request); }
token is a singleton instance of org.apache.struts.util.TokenProcessor. As you can see, these methods are just wrappers.
Here are the TokenProcessor implementations:
public synchronized String generateToken(HttpServletRequest request) {
HttpSession session = request.getSession(); try { byte id[] = session.getId().getBytes(); long current = System.currentTimeMillis(); if (current == previous) { current++; } previous = current; byte now[] = new Long(current).toString().getBytes(); MessageDigest md = MessageDigest.getInstance("MD5"); md.update(id); md.update(now); return toHex(md.digest()); } catch (NoSuchAlgorithmException e) { return null; }
}
public synchronized boolean isTokenValid( HttpServletRequest request, boolean reset) {
// Retrieve the current session for this request HttpSession session = request.getSession(false); if (session == null) { return false; }
// Retrieve the transaction token from this session, and
// reset it if requested
String saved = (String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
if (saved == null) {
return false;
}
if (reset) { this.resetToken(request); }
// Retrieve the transaction token included in this request String token = request.getParameter(Constants.TOKEN_KEY); if (token == null) { return false; }
return saved.equals(token); }
public synchronized void resetToken(HttpServletRequest request) {
HttpSession session = request.getSession(false); if (session == null) { return; } session.removeAttribute(Globals.TRANSACTION_TOKEN_KEY); }
public synchronized void saveToken(HttpServletRequest request) {
HttpSession session = request.getSession(); String token = generateToken(request); if (token != null) { session.setAttribute(Globals.TRANSACTION_TOKEN_KEY, token); }
}
And finally, this method is part of org.apache.struts.taglib.html.FormTag:
protected String renderToken() { StringBuffer results = new StringBuffer(); HttpSession session = pageContext.getSession();
if (session != null) {
String token =
(String) session.getAttribute(Globals.TRANSACTION_TOKEN_KEY);
if (token != null) {
results.append("<input type=\"hidden\" name=\"");
results.append(Constants.TOKEN_KEY);
results.append("\" value=\"");
results.append(token);
if (this.isXhtml()) {
results.append("\" />");
} else {
results.append("\">");
}
}
}
return results.toString(); }
Typically, in your setup Action's execute method, you will put tokens in place. A unique token will be saved as a HttpSession attribute. A copy of this token is inserted as a hidden form variable (this is done for you by the html:form tag) and goes to the user with the form. When the user submits the form, in comes the token -- it serves as a unique ID for a form. In your form processing Action's execute method, the first thing you will do is check the validity of this incoming token, by comparing it with the expected token (the HttpSession attribute). As soon as you finish checking the validity of the token (whether pass or fail), you should eliminate the token from the HttpSession attributes (the check and reset should be atomic as an execute method allows concurrent access -- there is a synchronized method of the singleton that provides this) -- that token has been processed and is no longer useful. Now if a repeat submit arrives with a duplicate form ID, the validity check will fail, as the HttpSession no longer carries that value as an attribute -- it either carries a new value or no value at all. This ensures that the form processing Action can only occur once for a given form.
A typical way do it:
Setup Action.execute: { . . . //setup form/populate if necessary . . . saveToken(request); // generates token; inserts HttpSession attribute; //form tag will use this when sending the response //that contains the form, including it as a hidden variable return mapping.findForward("success"); }
Form processing Action.execute:
{
. . .
//check credentials, etc.
. . .
//check and reset token as an atomic operation
if (!isTokenValid(request, true)) throw new Exception("invalid transaction token");
. . .
//optional -- saveToken(request);
//token is valid, continue processing
//optional -- resetToken(request);
. . .
}
Personally I prefer to use a token processor that uses parameter/attribute keys that are more specific (not one key for all forms but one key per logical form). This allows the user to have more than one form "checked out" and to submit them in any order, but still prevents duplicate submits of the same form. But the idea is basically the same.
Also, there can be some trickiness involved if the "continue processing" block of code above might encounter a manager-layer validation failure, and declarative Exception handling directs the user back to the form -- this will be a "new" form, so you will need to embed fresh tokens, or the user will correct his input, resubmit the form and encounter a duplicate submit error and then get really frustrated (see "optional" code above).
Erik
Brad Balmer wrote:
Do people use tokens to disable multiple requests? I have pages that use images to submit forms, thus I cannot use javascript to disable the submit on multiple clicks. I was reading about tokens but couldn't find a full example except the three functions to call.
If you can use tokens to disable multiple submits, does anybody have a full working example of how to use them?
Thanks.
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]