Sunteți pe pagina 1din 5

3/5/2011

Exploding Java: Simple Synchronizer

Friday, 13 March 2009

Simple Synchronizer Token with Spring MVC


The Problem If you have used Struts 1.x, you'll probably be familiar with using the synchronizer token functionality provided through the saveToken() and isTokenValid() methods of the Action class. These prevent duplicate form submission, either as a result of double-clicking a submit button, or trying to submit a form from the browser history after using the back button. Out of the box, Spring MVC doesn't have similar functionality, although it is being addressed with Spring Web Flow: How to prevent user from double clicking the submit in a form using spring MVC AbstractTokenTransactionSynchronizer So how could you use the synchronizer token pattern with Spring MVC if you aren't using Spring Web Flow? Synchronizer Token The basic idea of the synchronizer token pattern is that you keep a value in session scope that marks a point in the flow of the web application. As each form is rendered, it includes the value of the token from that point in time. On submission, the value that was embedded in the form is included in the request. The application can then compare the "historical" request token against the current session token. If the two are the same, processing continues and the session token is given a new value, effectively making the form out of date. If the two are different, it means that the form's token is lagging behind the current session token, i.e. the form has already been submitted. So, in implementing the synchronizer token pattern, there are three components to deal with. Firstly, providing and managing the session token itself; secondly, having forms embed and submit the historical value of the token; thirdly, providing a mechanism for the application to check the request and session tokens and act accordingly. 1. Managing the Session Token The way I choose to do this with Spring MVC is to borrow some ideas from the way that Struts 2 addresses the problem. I start off with a session listener to set up the token in session scope: TokenListener.java package mypackage; import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSessionEvent; import javax.servlet.http.HttpSessionListener; public class TokenListener implements HttpSessionListener { @Override public void sessionCreated(HttpSessionEvent sessionEvent) { HttpSession session = sessionEvent.getSession(); session.setAttribute( TokenFormController.getTokenKey() , TokenFormController.nextToken() ); } }

Blog Archive
2 0 1 0 (1 2 ) 2 0 0 9 (9 ) O c tobe r (1 ) A pril (2 ) M arc h (6 ) Les s than I ntuitive I nformation from the A O P JoinP oint Simple S ync hronizer T oken with S pring M V C C olour S c he mes C ompos ite V ie ws with J SP E ntering the B logos phere

Text Chat
C hat with Richard Senior

Related Blogs
Luk ewarm Jav a M M M , S oftware T he Balus C C ode

Labels
aop (1 ) apac he (1 ) artifac tory (1 ) bas h (1 ) c ac he c ontrol (1 ) c s s (1 ) ec lips e (2 ) ejb3 (2 ) glas s fis h (3 ) hibernate (1 ) huds on (1 ) java (3 ) jpa (2 ) js f (2 ) js p (1 ) js r-3 0 3 (1 ) junit (1 ) linux (2 ) lis te ner (1 ) logging (1 ) mave n (3 ) mys ql (1 ) netbeans (3 ) ope njdk (1 ) orac le (1 ) s erv le t (1 ) s itemes h (1 ) s pring (2 ) s pring mv c (2 ) s truts (1 ) s ubvers ion (1 ) s un (1 ) s ync hronizer tok en (1 ) tag library (1 ) tiles (1 ) tomc at (3 ) toplink (1 ) twitter (1 ) ubuntu (5 ) we b des ign (1 ) xml (1 )

This listener has to be declared in web.xml of course: web.xml ... <listener> <listener-class>mypackage.TokenListener</listener-class> </listener> ...

We'll see the code for the TokenFormC ontroller in a moment. For the time being, note that the session listener is fired whenever the container creates a session and it initialises the synchronizer token with a generated value. 2. Embedding the Historical Token in the Form When we want to protect a form from duplicate submission, we need to capture the value of the token, embed it in the form as it is rendered and have the historical value submitted along with the other form data. The obvious way to do this is with a hidden field in the form. Rather than worry about exactly how to do this in each form, I use a simple tag: /WEB-INF/tags/token.tag <%@tag description="Synchronizer Token" import="mypackage.TokenFormController" %> <input type="hidden" name="<%=TokenFormController.getTokenKey()%>" value="<%=session.getAttribute(TokenFormController.getTokenKey())%>" />

This is easily included in the form JSP:

blogspot.com//spring-mvc-synchroni

1/5

3/5/2011

Exploding Java: Simple Synchronizer


myInputForm.jsp <%@ taglib prefix="tags" tagdir="/WEB-INF/tags/" %> ... <form:form action="myAction.action"> ... <tags:token/> ... </form:form>

This example is written as if the Spring MVC dispatcher servlet is mapped to *.action . Obviously it doesn't matter and this is not a convention commonly used for Spring MVC but it keeps things clear for this example. 3. The TokenFormController I find the simplest and most transparent way of getting the token functionality into form controllers is to use a custom controller derived from the SimpleFormController hierarchy. This subclass provides token checking and routing to an "invalid token" view. In my example the controller also handles the generation of the next token value and defines the name of the token attribute. We've seen this in action in steps 1 & 2. You may prefer to factor this out into a separate class. As I tend to use CancellableFormController for most of my input forms, I've created my TokenFormController as a subclass of that. Here is the complete code: TokenFormController.java

blogspot.com//spring-mvc-synchroni

2/5

3/5/2011

Exploding Java: Simple Synchronizer


package mypackage; import import import import import import import java.util.Random; javax.servlet.http.HttpServletRequest; javax.servlet.http.HttpServletResponse; javax.servlet.http.HttpSession; org.springframework.validation.BindException; org.springframework.web.servlet.ModelAndView; org.springframework.web.servlet.mvc.CancellableFormController;

public class TokenFormController extends CancellableFormController { private static final String TOKEN_KEY = "_synchronizerToken"; private String invalidTokenView; @Override protected ModelAndView onSubmit( HttpServletRequest request , HttpServletResponse response , Object command , BindException errors ) throws Exception { if (isTokenValid(request)) { return super.onSubmit(request, response, command, errors); } return new ModelAndView(invalidTokenView); } private synchronized boolean isTokenValid(HttpServletRequest request) { HttpSession session = request.getSession(); String sessionToken = (String)session.getAttribute(getTokenKey()); String requestToken = request.getParameter(getTokenKey()); if (requestToken == null) { // The hidden field wasn't provided throw new TokenException("Missing synchronizer token in request"); } if (sessionToken == null) { // The session has lost the token. throw new TokenException("Missing synchronizer token in session"); } if (sessionToken.equals(requestToken)) { // Accept the submission and increment the token so this form can't // be submitted again ... session.setAttribute(getTokenKey(), nextToken()); return true; } return false; } public static String nextToken() { long seed = System.currentTimeMillis(); Random r = new Random(); r.setSeed(seed); return Long.toString(seed) + Long.toString(Math.abs(r.nextLong())); } public String getInvalidTokenView() { return invalidTokenView; } public void setInvalidTokenView(String invalidTokenView) { this.invalidTokenView = invalidTokenView; } public static String getTokenKey() { return TOKEN_KEY; } }

It's all fairly straightforward if you are familar with the way the SimpleFormController hierarchy fits together. The onSubmit() method ties into the standard controller flow - you just override the usual doSubmitAction() , formBackingObject(), and associated methods in your subclass to provide the controller functionality for your input form. You can pretty much forget about token processing. You'll need the TokenException unchecked exception class but this is just a trivial subclass of RuntimeException. The isTokenValid() method deals with the token checking. The nextToken() and getTokenKey() methods provide the next value and the name of the token respectively. Refer back to the session listener and tag to see how they make use of these. The string attribute invalidTokenView , which is returned as the view name of the returned ModelAndView if the request and session tokens don't match, is injected using the dispatcher servlet xml file. Just the same as the way the cancelView property works with the standard CancellableFormController . A typical configuration in the dispatcher servlet xml file would look something like this: dispatcher-servlet.xml

blogspot.com//spring-mvc-synchroni

3/5

3/5/2011

Exploding Java: Simple Synchronizer


<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="myAction.action">myActionController</prop> ... </props> </property> </bean> ... <bean id="myActionController" class="mypackage.MyActionController"> <property name="useCacheControlHeader" value="false"/> ... <!-- Required for CancellableFormController --> <property name="cancelParamKey" value="cancel"/> <property name="cancelView" value="redirect:myCancel.action"/> <!-- Standard FormController properties --> <property name="formView" value="myInputForm.jsp"/> <property name="successView" value="redirect:mySuccess.action"/> <!-- Required for the TokenFormController --> <property name="invalidTokenView" value="invalidToken.jsp"/> </bean>

For clarity, I've omitted other properties I usually have in here, e.g. a service facade bean that provides access to the Model, and validator references. MyActionController is the subclass of TokenFormController that manages the input form. That's about it. The only subtlety here, if you want the same back-button behaviour as you might expect from Struts 1.x, is the useCacheControlHeader. Setting this to false (the default is true from AbstractFormController), prevents the browser from getting a fresh copy of the input form as it works back through history. You'll probably want to do some tweaking if you want to use this in your applications, but hopefully that's enough to give you some ideas about how to implement the synchronizer token pattern with Spring MVC . I've found it quite a productive method. Posted by Richard Senior at 6:26 PM Labels: cache control, listener, spring mvc, synchronizer token, tag library

9 comments: Anonymous said... Hi, Nice job. However, I think that you will have to synchronize the isTokenValid method in order to prevent two submits to enter on the same time in the method and both to be considered valid. Wed Apr 08, 03:03:00 PM BST san-ho-zay said... Thank you for your comment. It's good to know that someone is reading! You make a good point and I have amended the code to reflect it. Fri Apr 10, 01:42:00 PM BST Anonymous said... Does this setup work for only one specific form, or can you apply this for multiple forms? If so, would you just enter multiple jsp's and action classes where applicable? Mon Apr 27, 11:11:00 PM BST san-ho-zay said... Once you've created the TokenFormC ontroller and token tag file, you can re-use them as often as you want. You'd typically have multiple controllers extending TokenFormC ontroller. Obviously each one would be configured slightly differently in dispatcher-servlet.xml. Then, once it's all tied together through the XML file, all you need to remember to do is use the token tag in each JSP that generates a submit. Tue Apr 28, 11:00:00 AM BST C hris B said... Seems good if you don't want to jump to Web Flow. A couple of concerns though: 1) The first click will get processed but the user won't see the output; instead they'll see some error page (and may assume the transaction has failed and repeat it manually). 2) We have '@C ontroller' annotated POJO classes, rather than subclassing any Spring MVC controller class. Not quite sure how best to use this approach. Thu May 21, 04:43:00 PM BST san-ho-zay said... The first point is very valid and has always been a potential problem with this approach in Struts. Obviously the user can't resumbit the filled in form again and I guess it's down to the application applying case-specific validation on any further transactions. Ever used one of those self-service checkouts at the supermarket? They use a timeout so that you don't double-submit a tin of beans as you wave it around under the laser. It still has to allow you to put another tin of beans through afterwards though. With your annotations, could you use your own base class containing the token processing? Sun May 31, 06:27:00 PM BST Anonymous said... 'I think that you will have to synchronize the isTokenValid method in order to prevent two submits to enter' C ontrollers are supposed to be thread-safe are they not? Otherwise why not synchro on protected ModelAndView onSubmit also? Tue Mar 30, 02:29:00 PM BST san-ho-zay said... C ontrollers are called from the dispatcher servlet and, as with any servlet, run in the scope of individual threads on a single instance of the servlet. Potential exists for synchronization issues where multiple threads are updating a shared resource. In this case, the token, stored in session scope, is a shared resource for multiple threads in the same browser session. You could argue that protecting the isTokenValid() method is ultra-defensive programming, on the basis that you would have to work quite hard to get two threads in the same browser session submitting protected forms at exactly the same time. Nevertheless, the possibility exists and, as synchronization is unlikely to incur much performance overhead in terms of threads waiting, it seems reasonable to adopt the ultra-defensive approach.

blogspot.com//spring-mvc-synchroni

4/5

3/5/2011

Exploding Java: Simple Synchronizer


Tue Mar 30, 09:22:00 PM BST Anonymous said... How does this solution take into accoutn other Ajax requests from same form ? Tue Sep 14, 02:48:00 PM BST Post a C omment Newer Post Subscribe to: Post C omments (Atom) Home Older Post

Use The Google Homepage Get More Relevant Results From Your First Search. Use Google Homepage! Free Web Integration Try the Convertigo enterprise mashup platform free today! www.convertigo.com Bridge Java and .NET Extreme performance, trivial deployment, drop-dead easy www.CodeMesh.com

Google.co.in

Followers
Follow
w ith Google Friend Connect

Followers (4)

Already a member? Sign in

blogspot.com//spring-mvc-synchroni

5/5

S-ar putea să vă placă și