Exploding Java - Simple Synchronizer Token With Spring MVC
Exploding Java - Simple Synchronizer Token With Spring MVC
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())%>" />
blogspot.com//spring-mvc-synchroni
1/5
3/5/2011
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
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
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
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)
blogspot.com//spring-mvc-synchroni
5/5