CSRF protection with Spring Security revisited

January 29, 2014 by Michael

At the end of last year, Spring Security 3.2 was released and brought a lot of new features, among them a built-in “Cross Site Request Forgery” protection”.

Nearly two years earlier i wrote my CSRF protection implementation with Spring Security 3.1, have a look here.

I really like the built-in implementation and most of it is very similar to my solution. The main difference is that the protection is at security filter level and not at application level like mine was. Also they use a token class for encapsulating the tokens name and value.

My solution can be very easily adapted to Spring Security 3.2.

First of all, configure it with the onliner

<csrf  />

or use the new annotation based configuration method.

Then throw away everything from my solution except the CSRFTokenTag. Edit the later one to contain the following code:

import java.io.IOException;
import java.util.Random;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
 
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.security.web.csrf.CsrfTokenRepository;
 
/**
 * Creates a hidden input field with the CSRF Token
 * @author michael.simons, 2011-09-20
 */
@Configurable
public class CSRFTokenTag extends TagSupport {
	private final static Random random = new Random(System.currentTimeMillis());
 
	private static final long serialVersionUID = 745177955805541350L;
 
	private boolean plainToken = false;
	private String elementId;
 
	@Autowired
	private CsrfTokenRepository csrfTokenRepository;
 
	@Override
	public int doStartTag() throws JspException {		
		final CsrfToken token = csrfTokenRepository.loadToken((HttpServletRequest) super.pageContext.getRequest());
		if(token != null)
			try {
				if(plainToken)
					pageContext.getOut().write(token.getToken());
				else
					pageContext.getOut().write(String.format("<input type=\"hidden\" name=\"%s\" id=\"%s\" value=\"%s\" />", token.getParameterName(), StringUtils.isNotBlank(this.elementId) ? this.elementId : String.format("%s_%d", token.getParameterName(), random.nextInt()), token.getToken()));
			} catch (IOException e) {
			}
		return SKIP_BODY;
	}
 
	@Override
	public int doEndTag() throws JspException {
		return EVAL_PAGE;
	}
 
	public boolean isPlainToken() {
		return plainToken;
	}
 
	public void setPlainToken(boolean plainToken) {
		this.plainToken = plainToken;
	}
 
	public String getElementId() {
		return elementId;
	}
 
	public void setElementId(String elementId) {
		this.elementId = elementId;
	}	
}

The guys at Spring have a nice suggestions for including the token for AJAX/Jsons request. The new filter also validates request headers. The recommend adding the header name and token value to the pages meta information like so

  <meta name="_csrf" content="${_csrf.token}"/>
  <meta name="_csrf_header" content="${_csrf.headerName}"/>

and then manually add the header to each JavaScript request made with jQuery.

An alternative for jQuery users would be the following pre filter:

$.ajaxPrefilter(function(options, originalOptions, jqXHR) {
	var token = $("meta[name='_csrf']").attr("content");
	var header = $("meta[name='_csrf_header']").attr("content");		  
	jqXHR.setRequestHeader(header, token);
});

I’m happy to be able to get rid of some code of mine, though the solution worked quite well for 2 years now.

No comments yet

One Trackback/Pingback
  1. […] Note: This tutorial is for Spring Security 3.1, an updated version that uses the build-in CSRF protection of Spring Security 3.2 can be found here […]

Post a Comment

Your email is never published. We need your name and email address only for verifying a legitimate comment. For more information, a copy of your saved data or a request to delete any data under this address, please send a short notice to michael@simons.ac from the address you used to comment on this entry.
By entering and submitting a comment, wether with or without name or email address, you'll agree that all data you have entered including your IP address will be checked and stored for a limited time by Automattic Inc., 60 29th Street #343, San Francisco, CA 94110-4929, USA. only for the purpose of avoiding spam. You can deny further storage of your data by sending an email to support@wordpress.com, with subject “Deletion of Data stored by Akismet”.
Required fields are marked *