Struts2 Token Interceptor Example

Filed Under: Struts 2

Struts 2 token interceptor can be used to handle multiple form submission problem. While designing web application, sometimes we have to make sure that double form submission is treated as duplicate request and not be processed. For example, if user reloads the online payment form and there are not enough checks in place to identify it as duplicate request, customer will be charged twice.

Double form submission problem handling needs to be done both at client side and server side. In client side, we can disable the submit button, disable back button but there will always be options through which user can send the form data again. Struts2 provides token interceptors that are designed to deal with this particular problem.

Struts2 Token Interceptor

There are two interceptors defined in struts-default package as:


<interceptor name="token" class="org.apache.struts2.interceptor.TokenInterceptor"/>
<interceptor name="tokenSession" class="org.apache.struts2.interceptor.TokenSessionStoreInterceptor"/>

These interceptors are not part of any predefined interceptor stack because if we add it for any action, the form submitted should have a token parameter else it will throw exception. We will look it’s usage with a simple project. Final project structure will look like below image.

Struts2 token interceptor

Struts2 Token Interceptor Example Configuration Files

web.xml


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">
  <display-name>Struts2TokenInterceptor</display-name>

	<filter>
		<filter-name>struts2</filter-name>
		<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
	</filter>

	<filter-mapping>
		<filter-name>struts2</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

</web-app>

Deployment descriptor is configured to use Struts 2 framework.

pom.xml


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>Struts2TokenInterceptor</groupId>
	<artifactId>Struts2TokenInterceptor</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<build>
		<plugins>
			<plugin>
				<artifactId>maven-compiler-plugin</artifactId>
				<version>3.1</version>
				<configuration>
					<source>1.6</source>
					<target>1.6</target>
				</configuration>
			</plugin>
			<plugin>
				<artifactId>maven-war-plugin</artifactId>
				<version>2.3</version>
				<configuration>
					<warSourceDirectory>WebContent</warSourceDirectory>
					<failOnMissingWebXml>false</failOnMissingWebXml>
				</configuration>
			</plugin>
		</plugins>
		<finalName>${project.artifactId}</finalName>
	</build>
	<dependencies>
		<dependency>
			<groupId>org.apache.struts</groupId>
			<artifactId>struts2-core</artifactId>
			<version>2.3.15.1</version>
		</dependency>
	</dependencies>
</project>

The web application is configured as maven project where we have added struts2-core dependency.

struts.xml


<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
	"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
	"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
	<!-- constant to define result path locations to project root directory -->
	<constant name="struts.convention.result.path" value="/"></constant>

	<package name="user" namespace="/" extends="struts-default">
		<action name="update">
			<result>/update.jsp</result>
		</action>
		<action name="UpdateUser" class="com.journaldev.struts2.actions.UpdateUserAction">
			<interceptor-ref name="token"></interceptor-ref>
			<!--
			OR <interceptor-ref name="tokenSession"></interceptor-ref>
			 -->
			<interceptor-ref name="defaultStack"></interceptor-ref>
			<result name="success">/update_success.jsp</result>
			<result name="input">/update.jsp</result>
			<result name="invalid.token">/invalid_token.jsp</result>
		</action>
	</package>

</struts>
  1. We can use either token interceptor or tokenSession interceptor with any action.
  2. If token interceptor identifies the request as duplicate, then it returns the result invalid.token, that’s why we have a result configured for this.
  3. If form field validation fails then input result is returned where we are returning the same page from where we get the request.

We will look into the complete flow once we have seen the implementation and application behavior with duplicate request.

Struts2 Token Interceptor Example Action Class

UpdateUserAction.java


package com.journaldev.struts2.actions;

import java.util.Date;

import com.opensymphony.xwork2.ActionSupport;

public class UpdateUserAction extends ActionSupport {

	@Override
	public String execute() {
		System.out.println("Update Request Arrived to Action Class");
		//setting update time in action class
		setUpdateTime(new Date());
		return SUCCESS;
	}

	@Override
	public void validate(){
		if(isEmpty(getName())){
			addActionError("Name can't be empty");
		}
		if(isEmpty(getAddress())){
			addActionError("Address can't be empty");
		}
	}

	//java bean variables
	private String name;
	private String address;
	private Date updateTime;
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		this.address = address;
	}

	public Date getUpdateTime() {
		return updateTime;
	}

	public void setUpdateTime(Date updateTime) {
		this.updateTime = updateTime;
	}

	private boolean isEmpty(String str) {

		return str == null ? true:(str.equals("") ? true:false);
	}

}

A simple action class with basic form fields validation and some java bean properties. Notice that update time is set by action class, it has been added to show the application behavior when we use tokenSession interceptor.

Struts2 Token Interceptor Example JSP Pages

update.jsp


<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Update User Request Page</title>
</head>
<body>
<s:if test="hasActionErrors()">
<s:actionerror/>
</s:if>
<br>
<s:form action="UpdateUser">
<s:textfield name="name" label="User Name"></s:textfield>
<s:textfield name="address" label="Address"></s:textfield>
<s:submit name="submit" value="Update"></s:submit>
<%-- add token to JSP to be used by Token interceptor --%>
<s:token />
</s:form>
</body>
</html>

The entry point of the application from where user will submit form to update some information. We are using actionerror tag to show any validation errors added by the application. The most important point to note is s:token tag that will be used by token interceptors in making sure duplicate requests are not getting processed.

update_success.jsp


<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Update User Success Page</title>
</head>
<body>
<h3>User information updated successfully.</h3>

Name: <s:property value="name"/><br>
Address: <s:property value="address"/><br>
Update Time: <s:date name="updateTime"/><br>

<h4>Thank You!</h4>
</body>
</html>

Simple JSP page showing action class java bean properties.

invalid_token.jsp


<%@ page language="java" contentType="text/html; charset=US-ASCII"
    pageEncoding="US-ASCII"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=US-ASCII">
<title>Update Duplicate Request Page</title>
</head>
<body>
<h3>User information is not updated, duplicate request detected.</h3>
<h4>Possible Reasons are:</h4>
<ul>
	<li>Back button usage to submit form again</li>
	<li>Double click on Submit button</li>
	<li>Using "Reload" Option in browser</li>
</ul>
<br>
<s:if test="hasActionErrors()">
	<s:actionerror/>
</s:if>
</body>
</html>

Simple JSP page showing different methods that can cause multiple form submissions, notice the actionerror tag usage.

Now when we will run our application, we will see following pages as response in the same order.

Struts2 token

Struts2 token success

Struts2 token reload page

Struts2 token invalid

If you will look into the source of input page, you will see that Struts2 API has converted token tag to following HTML snippet.


<input type="hidden" name="struts.token.name" value="token" />
<input type="hidden" name="token" value="HGWQI7ZGP7KFGJLDPNTSFHLUX5RF26IK" />

Also you will notice following logs snippet.


Update Request Arrived to Action Class
WARNING: Form token HGWQI7ZGP7KFGJLDPNTSFHLUX5RF26IK does not match the session token null.

Notice that duplicate request doesn’t even reach to action class and token interceptor returns the invalid.token page as response.

If you will use tokenSession interceptor, you will notice that it returns the same response as the first request. You can confirm this by going back and edit form fields and then submitting form again. The response update time and field values will be old values as sent in the first request.

How Struts2 Token Interceptor Works

Now let’s see how token interceptor works to handle multiple form submissions.

  1. When a request is made to the update action, Struts2 tags API generates a unique token and set it to the session. The same token is sent in the HTML response as hidden field.
  2. When the form is submitted with token, it is intercepted by token interceptor where it tries to fetch the token from the session and validate that it’s same as the token received in the request form. If token is found in session and validated then the request is forwarded to the next interceptor in the chain. Token interceptor also removes the token from the session.
  3. When the same form is submitted again, token interceptor will not find it in the session. So it will add an action error message and return invalid.token result as response. You can see this message in above image for invalid_token.jsp response. This way token interceptor make sure that a form with token is processed only once by the action.
  4. If we use tokenSession interceptor, rather than returning invalid token response, it tries to return the same response as the returned by the first action with same token. This implementation is done in the TokenSessionStoreInterceptor class that saves the response for each token in the session.
  5. We can override the action error message sent by token interceptor through i18n support with key as “struts.messages.invalid.token”.

Thats all for the usage of Struts2 token interceptor to handle multiple form submission problem in web application. Download the application from below link and play around with it for better understanding.

Comments

  1. Pawan says:

    I am using struts2 framework . when I submit a form it gets stored in database successfully and I redirect the user to the same page but when I refresh that page again I get below error.

    I am using tokenSession interceptor.

    Can someone please help how to get rid of this problem?

    Error 500–Internal Server Error
    java.lang.NullPointerException
    at weblogic.servlet.internal.ServletRequestImpl$SessionHelper.initSessionInfo(ServletRequestImpl.java:3193)
    at weblogic.servlet.internal.ServletRequestImpl$SessionHelper._getSessionInternal(ServletRequestImpl.java:2894)
    at weblogic.servlet.internal.ServletRequestImpl$SessionHelper.getSessionInternal(ServletRequestImpl.java:2881)
    at weblogic.servlet.internal.ServletRequestImpl$SessionHelper.getSession(ServletRequestImpl.java:2871)
    at weblogic.servlet.internal.ServletRequestImpl.getSession(ServletRequestImpl.java:1525)
    at weblogic.servlet.internal.RequestDispatcherImpl.include(RequestDispatcherImpl.java:508)
    at weblogic.servlet.jsp.PageContextImpl.include(PageContextImpl.java:161)
    at org.apache.struts2.result.ServletDispatcherResult.doExecute(ServletDispatcherResult.java:132)
    at org.apache.struts2.result.StrutsResultSupport.execute(StrutsResultSupport.java:206)
    at org.apache.struts2.interceptor.TokenSessionStoreInterceptor.handleInvalidToken(TokenSessionStoreInterceptor.java:143)
    at org.apache.struts2.interceptor.TokenSessionStoreInterceptor.handleToken(TokenSessionStoreInterceptor.java:109)
    at org.apache.struts2.interceptor.TokenInterceptor.doIntercept(TokenInterceptor.java:139)
    at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:99)
    at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:249)
    at org.apache.struts2.factory.StrutsActionProxy.execute(StrutsActionProxy.java:48)
    at org.apache.struts2.dispatcher.Dispa

  2. Krupaharan says:

    Examples are so nice and easy to understand. Thanks.
    I’ve one question, How to implement Token interceptor in jQuery.ajax() ? Could you provide some samples/eg.

  3. zhangwei says:

    Thanks Pankaj, its helpful for me. But now I have a question:

    the code in TokenSessionStoreInterceptor such as :

    HttpSession session = ServletActionContext.getRequest().getSession(true);
    synchronized (session) {
    if (!TokenHelper.validToken()) {
    return handleInvalidToken(invocation);
    }
    return handleValidToken(invocation);
    }

    My web application in 2 tomcats, and store the session into memcahced, so maybe the synchronized is not work, right?

    1. Eddy says:

      You are right. synchronized only works on same JVM.

  4. HARSHAD R says:

    Token Interceptor works perfectly if the action class for which the interceptor is applied “extends ActionSupport”. But if the same action class is rewritten with “implements Action” logic and we run the project it throws the following error :

    java.lang.NullPointerException com.opensymphony.xwork2.util.LocalizedTextUtil.findText(LocalizedTextUtil.java:630)

    May i know why ?

    i am yet to see ActionSupport in detail. Is it because localized error support is part of ActionSupport class or for some other reason ?

  5. Joseph Ketcham says:

    Adding some information from my experience with this. When I utilized the token interceptor I kept getting this on my page when I ran a proper single submit:

    struts2 developer notification Error setting expression ‘token’ with value …

    What this is saying is we need to add a get and set for String token in the action java class. So in your UpdateUserAction.java above add in a declaration

    String token;

    then the usual set/get

    public String getToken() {
    return token;
    }

    public void setToken(String token) {
    this.token = token;
    }

    This will eliminate the developer notification and allow the token interceptor to work.

    ——————————————————————————————–

    Also to explain, the token interceptor does the usual stop and present a page with the invalid_token.jsp page. You don’t have to do that. It’s better to use

    This interceptor expanded beyond the first token interceptor so it prevents the double submit but allows the first to go through and allows the application to process and flow as if the double submit never happened. Still need to have the token tag in the form as stated above but the handling is much improved!

    1. Joseph Ketcham says:

      nuts got hit by the posting filter on part of this..

      In the second to last paragraph I said it’s better to use..add in:

      interceptor-ref name=”tokenSession”

      1. Joseph Ketcham says:

        http://struts.apache.org/release/2.3.x/docs/token-session-interceptor.html

        Token Session Interceptor

        This interceptor builds off of the TokenInterceptor, providing advanced logic for handling invalid tokens. Unlike the normal token interceptor, this interceptor will attempt to provide intelligent fail-over in the event of multiple requests using the same session. That is, it will block subsequent requests until the first request is complete, and then instead of returning the invalid.token code, it will attempt to display the same response that the original, valid action invocation would have displayed if no multiple requests were submitted in the first place.

        NOTE: As this method extends off MethodFilterInterceptor, it is capable of deciding if it is applicable only to selective methods in the action class. See MethodFilterInterceptor for more info.

  6. Saiprasad says:

    Nice and very useful article

  7. Guilherme says:

    I guess there must be a field String token in your Action Class otherwise it will throw Unexpected Exception caught setting ‘token’ on ‘class XYZ: Error setting expression ‘token’ with value XXX

  8. Mariyappa says:

    Where can i use this tags? Please explain to me?

  9. Cr7 says:

    thanks~ you Your examples is very nice ,~ very useful!!!

  10. sushil k bhaskar says:

    Thanks Mr. Pankaj,You rock

  11. Manish Dass says:

    Really Thanks Pankaj….
    its so helpful…

Leave a Reply

Your email address will not be published. Required fields are marked *

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages