Hey, we are using Spring Security 2.0.4. We have a TransactionTokenBean which generates a unique token each POST, the bean is session scoped. The token is used for the duplicate form submission problem (and security). The TransactionTokenBean is called from a Servlet filter. Our problem is the following, after a session timeout occured, when you do a POST in the application Spring Security redirects to the logon page, saving the original request. After logging on again the TransactionTokenBean is created again, since it is session scoped, but then Spring forwards to the originally accessed url, also sending the token that was generated at that time. Since the TransactionTokenBean is created again, the tokens do not match and our filter throws an Exception. I don't quite know how to handle this elegantly, (or for that matter, I can't even fix it with a hack), any ideas?
This is the code of the TransactionTokenBean:
public class TransactionTokenBean implements Serializable {
public static final int TOKEN_LENGTH = 8;
private RandomizerBean randomizer;
private transient Logger logger;
private String expectedToken;
public String getUniqueToken() {
return expectedToken;
}
public void init() {
resetUniqueToken();
}
public final void verifyAndResetUniqueToken(String actualToken) {
verifyUniqueToken(actualToken);
resetUniqueToken();
}
public void resetUniqueToken() {
expectedToken = randomizer.getRandomString(TOKEN_LENGTH, RandomizerBean.ALPHANUMERICS);
getLogger().debug("reset token to: " + expectedToken);
}
public void verifyUniqueToken(String actualToken) {
if (getLogger().isDebugEnabled()) {
getLogger().debug("verifying token. expected=" + expectedToken + ", actual=" + actualToken);
}
if (expectedToken == null || actualToken == null || !isValidToken(actualToken)) {
throw new IllegalArgumentException("missing or invalid transaction token");
}
if (!expectedToken.equals(actualToken)) {
throw new InvalidTokenException();
}
}
private boolean isValidToken(String actualToken) {
return StringUtils.isAlphanumeric(actualToken);
}
public void setRandomizer(RandomizerBean randomizer) {
this.randomizer = randomizer;
}
private Logger getLogger() {
if (logger == null) {
logger = Logger.getLogger(TransactionTokenBean.class);
}
return logger;
}
}
and this is the Servlet filter (ignore the Ajax stuff):
public class SecurityFilter implements Filter {
static final String AJAX_TOKEN_PARAM = "ATXTOKEN";
static final String TOKEN_PARAM = "TXTOKEN";
private WebApplicationContext webApplicationContext;
private Logger logger = Logger.getLogger(SecurityFilter.class);
public void init(FilterConfig config) {
setWebApplicationContext(WebApplicationContextUtils.getWebApplicationContext(config.getServletContext()));
}
public void destroy() {
}
public void doFilter(ServletRequest req, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
HttpServletRequest request = (HttpServletRequest) req;
if (isPostRequest(request)) {
if (isAjaxRequest(request)) {
log("verifying token for AJAX request " + request.getRequestURI());
getTransactionTokenBean(true).verifyUniqueToken(request.getParameter(AJAX_TOKEN_PARAM));
} else {
log("verifying and resetting token for non-AJAX request " + request.getRequestURI());
getTransactionTokenBean(false).verifyAndResetUniqueToken(request.getParameter(TOKEN_PARAM));
}
}
chain.doFilter(request, response);
}
private void log(String line) {
if (logger.isDebugEnabled()) {
logger.debug(line);
}
}
private boolean isPostRequest(HttpServletRequest request) {
return "POST".equals(request.getMethod().toUpperCase());
}
private boolean isAjaxRequest(HttpServletRequest request) {
return request.getParameter("AJAXREQUEST") != null;
}
private TransactionTokenBean getTransactionTokenBean(boolean ajax) {
return (TransactionTokenBean) webApplicationContext.getBean(ajax ? "ajaxTransactionTokenBean"
: "transactionTokenBean");
}
void setWebApplicationContext(WebApplicationContext context) {
this.webApplicationContext = context;
}
}
relevant part of web.xml:
<filter>
<filter-name>SecurityFilter</filter-name>
<filter-class>
xxx.common.web.security.SecurityFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>SecurityFilter</filter-name>
<servlet-name>SpringServlet</servlet-name>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
the TransactionTokenBean:
<bean id="transactionTokenBean" class="xxx.common.web.bean.support.TransactionTokenBean"
init-method="init" scope="session">
<property name="randomizer" ref="randomizer" />
</bean>