Unless I've missed something, neither SWF, Spring MVC, nor vanilla JSF implementations offer a built-in token mechanism, as in Struts. A token mechanism can be used to deal with multiple-submit issues on a page and to act as a defense to Cross Site Request Forgery (CSRF) attacks.
SWF's execution keys almost work as CSRF protection tokens, but execution keys increment predictably and they may stick around for a while, depending on your maximum executions and snapshots settings.
I haven't heard any followup on efforts to manually generate your own flow execution IDs, and even then that doesn't solve the multiple-submit issue.
I've got a potential solution, but it's still semi-automatic and doesn't completely address multiple submits. I thought I'd post it here and get some feedback and maybe suggestions for a better approach.
In transitions with the attribute "TOKEN_PROTECTED", it attempts to match the "GUARD_TOKEN" requestParameter with an attribute of the same name in viewScope. Through blatant hackery, the most I could get it to do was refresh the current page if the tokens don't match, and the act of refreshing the page brings the tokens back in sync, so it's a little weak for preventing multiple-submits, but then you'd probably use history="invalidate" for that anyway.
Code:/** * Generates guard tokens on view render (set as "GUARD_TOKEN" in viewScope) * and checks for a valid token requestParameter in transitions with the attribute * "TOKEN_PROTECTED". If no valid token is found, the transition is cancelled. */ public class FlowTokenGuardian extends FlowExecutionListenerAdapter { private static final String GUARD_TOKEN = "GUARD_TOKEN"; public void viewRendering(RequestContext requestContext, View view, StateDefinition stateDefinition) { requestContext.getViewScope().put(GUARD_TOKEN, UUID.randomUUID()); } public void transitionExecuting(RequestContext requestContext, TransitionDefinition transitionDefinition) { if (transitionDefinition.getAttributes().contains("TOKEN_PROTECTED")) { String guardTokenString = requestContext.getRequestParameters().get(GUARD_TOKEN); UUID guardToken = guardTokenString != null ? UUID.fromString(guardTokenString) : null; UUID viewToken = (UUID) requestContext.getViewScope().get(GUARD_TOKEN); if (guardToken == null || viewToken == null || !guardToken.equals(viewToken)) { Transition transition = (Transition) transitionDefinition; transition.setExecutionCriteria(new VetoedExecutionCriteria(transition)); } } } }Code:/** * Performs a one-time veto of the transition, then restores the previous * execution criteria. */ public class VetoedExecutionCriteria implements TransitionCriteria { private Transition transition; private TransitionCriteria previousExecutionCriteria; public VetoedExecutionCriteria(Transition transition) { this.transition = transition; this.previousExecutionCriteria = transition.getExecutionCriteria(); } /** * Restores the previously set execution criteria, but returns false this one * time. * @param requestContext * @return false */ public boolean test(RequestContext requestContext) { transition.setExecutionCriteria(previousExecutionCriteria); return false; } }


Reply With Quote
