CSRF protection in Spring Security

Spring Security project provides solutions for a lot of web dangers. One of them is CSRF.

In this article we'll examine how does Spring Security protect webapps against CSRF attacks. At the first part we'll try to define CSRF acronym. The second part will be given to CSRF protection in Spring Security from the technical point of view. At the last part we'll put CSRF protection in place.

What is CSRF ?

The CSRF acronym means Cross-Site Request Forgery and represents a kind of identity theft. To simplify, this kind of attack consists on forcing system's user to make non-desired actions. By non-desired action we understand an action which user's doesn't want to do. Confused ? Let's take a simple example to illustrate that. Imagine that user A is connected to your forum. Connected user has some privileges and it can, unlike non-connected one, add links to his profile. Now, hacker knows that and it sends a mail to user A containing an image which "src" attribute contain the link to add page, as in below sample:

Hello user,

If you don't laugh with below image, you are too serious !
<img src="http://www.myforumsite.com/add-link?linkName=http://www.hackersite.com" alt="Very funny image" />

When user A receives this mail, probably he won't see hidden image. But he'll see the alt attribute and probably download dangerous image from add links page (http://www.myforumsite.com/add-link?linkName=http://www.hackersite.com). If he uses browser-based mail client, he won't download the image but will add link http://www.hackersite.com to his profile.

To resume this situation, we can tell that CSRF is an exploit based on user's trust in his browser. User thinks that previously presented situation can't occur because his browser doesn't make URL calls without his confirmation.

Now when we understand the danger we can define the protection method. The most popular protection against CSRF attacks is CSRF protection token. This technique adds protection token to all sensible actions which user can make. It can be, for example, a form submit with POST method or simple actions made in GET. Usually, when the action is made from a form, token is added as a hidden input. Otherwise, it's appended to URL. The token is generated by the server which, before executing desired action, checks its validity.

Other protection method consists on checking HTTP Referrer header. If the request comes from authorized page, we consider it as valid. Otherwise, is denied. However, it's worse technique because checked headers may be absent. But their absence can be explained and it can mean a normal navigation and not an attack. Headers can also be faked by the hacker.In consequence, HTTP headers should be more used as an eventual detection system rather than a protection system.

CSRF in Spring Security

As other security mechanisms in Spring Security, CSRF is also based on filter. It's represented by org.springframework.security.web.csrf.CsrfFilter class. CSRF token is represented by CsrfToken interface which default implementation is DefualtCsrfToken. All tokens are stored in a repository implementing CsrfTokenRepository interface. The default repository is session-based and is represented by HttpSessionCsrfTokenRepository class. With the knowing about of all these elements, we can start to analyze filter's code source.

Every time when CsrfFilter is invoked, it starts by checking if CSRF token is present in request's attributes. If it's not the case, the filter tries to load it from repository and, if the case when the repository doesn't contain appropriate token, it generates new token with CsrfToken generateToken(HttpServletRequest request) method which implementation in HttpSessionCsrfTokenRepository looks like:

public CsrfToken generateToken(HttpServletRequest request) {
  return new DefaultCsrfToken(headerName, 
    parameterName, createNewToken());
}

private String createNewToken() {
  return UUID.randomUUID().toString();
}

After that, CsrfFilter uses an instance of RequestMatcher to detect if given request should be secured with CSRF token. If it's the case, user's token is retrieved either from HTTP header or from request parameter. Retrieved token is after compared with locally generated token. If both are different, one of two exceptions is thrown: MissingCsrfTokenException or InvalidCsrfTokenException. The first one occurs when the token wasn't found in token's repository at the begin of the filter. The second exception occurs when the found token is not the same as token generated locally. Both exceptions will produce an "access denied" situation. In the other side, if the test passes, security chain continues its execution. All checking fragment looks like:

String actualToken = request.getHeader(csrfToken.getHeaderName());
if(actualToken == null) {
  actualToken = request.getParameter(csrfToken.getParameterName());
}
if(!csrfToken.getToken().equals(actualToken)) {
  if(logger.isDebugEnabled()) {
    logger.debug("Invalid CSRF token found for " 
       + UrlUtils.buildFullRequestUrl(request));
  }
  if(missingToken) {
    accessDeniedHandler.handle(request, response, 
      new MissingCsrfTokenException(actualToken));
  } else {
    accessDeniedHandler.handle(request, response, 
      new InvalidCsrfTokenException(csrfToken, actualToken));
  }
  return;
}
filterChain.doFilter(request, response);

Tokens are saved in repository when they're read. It's made thanks to CsrfFilter's inner SaveOnAccessCsrfToken class and given method:

private void saveTokenIfNecessary() {
  if(this.tokenRepository == null) {
    return;
  }

  synchronized(this) {
    if(tokenRepository != null) {
      this.tokenRepository.
        saveToken(delegate, request, response);
      this.tokenRepository = null;
      this.request = null;
      this.response = null;
    }
  }
}

How CSRF tokens are saved in repository ? In the case of HttpSessionCsrfTokenRepository they're saved as session attributes:

public void saveToken(CsrfToken token, 
    HttpServletRequest request, HttpServletResponse response) {
  if (token == null) {
    HttpSession session = 
      request.getSession(false);
    if (session != null) {
      session.removeAttribute(sessionAttributeName);
    }
  } else {
    HttpSession session = request.getSession();
    session.setAttribute(sessionAttributeName, token);
  }
}

Attribute where user's CSRF token is stored is called org.springframework.security.web.csrf.HttpSessionCsrfTokenRepository.CSRF_TOKEN. By default, header's name which stores CSRF token is X-CSRF-TOKEN and parameter's name is _csrf.

RequestMatcher used to check if given request can be protected against CSRF attacks is inner class for CsrfFilter, DefaultRequiresCsrfMatcher. It accepts all requests not executed within GET, HEAD, TRACE or OPTIONS methods. In CsrfFilter's comment we can read that the decision to not handle these methods are dictated by the respect of REST semantics where only POST, PUT, DELETE and PATCH are allowed to modify the state.

Put in place CSRF with Spring Security

We'll put CSRF protection in project initiated previously, in the article about remember-me filter in Spring Security. To already existing configuration we need to add entry used to activate CSRF protection. Sample definition of csrf is sufficient to activate CSRF filter in given HTTP security context:

<security:http authentication-manager-ref="frontend" auto-config="true" use-expressions="true">
<security:form-login login-page="/login" default-target-url="/secret/data"
    authentication-failure-url="/login?error=true" password-parameter="password"
    username-parameter="login" login-processing-url="/do-login" />
  <security:remember-me data-source-ref="dataSource" key="secret_remember_me" />
  <security:logout logout-url="/logout" logout-success-url="/login" invalidate-session="true" delete-cookies="JSESSIONID" />
  <security:csrf />
</security:http>

Now we need only to put CSRF token in our form with <sec:csrfInput /> tag. After this change, our login form looks now like:

<%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<%@taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>

<form method="post" action="/do-login">
  <input type="text" name="login" placeholder="Your login" />
  <input type="password" name="password" placeholder="Your password" />
  remember me <input type="checkbox" name="_spring_security_remember_me" />
  <sec:csrfInput />
  <input type="submit" name="doLogin" value="Login" />
</form>

Note that normally CSRF input is added automatically when you're using Spring's <form></form> to generate form. When you want to insert CSRF tag inside header, you need to use <sec:csrfMetaTags /> tag. Now, we should be able to login correctly. But let's make one test to check the behaviour when CSRF token is corrupted (modified manually before submit). By doing that, we should get page with 403 response and message similar to "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'".

Note also that by adding <csrf /> inside <http /> configuration you deactivated the logout through GET request. Since now, the logout will only be possible with POST method. This override is made with org.springframework.security.config.http.AuthenticationConfigBuilder. It contains a private field called csrfLogoutHandler. This field represent the bean definition for CsrfLogoutHandler class. The definition is returned in BeanDefinition getCsrfLogoutHandler() method of org.springframework.security.config.http.CsrfBeanDefinitionParser class. If it doesn't return null, builder for <http /> considers that CSRF protection is active. In this case we consider that CSRF token need to be removed from user's session repository. New CSRF token will be generated for new user session. Code from CsrfLogoutHandler, in charge of clean repository, is:

public void logout(HttpServletRequest request, 
    HttpServletResponse response, Authentication authentication) {
  this.csrfTokenRepository.saveToken(null, request, response);
}

It will remove CSRF entry from current user session. However imagine that your client doesn't want to handle logout in with visual forms. We can change it in several steps. First of all, we need to add AJAX request which will call /logout URL in POST. Secondly, this request must contain either _csrf parameter or CSRF header. Because we've already discovered the first solution, we'll opt for the second one. To get header attributes, we'll use <sec:csrfMetaTags /> tag which should generate something as:

<meta name="_csrf_parameter" content="_csrf" />
<meta name="_csrf_header" content="X-CSRF-TOKEN" />
<meta name="_csrf" content="97950459-f9aa-4b29-9179-d702228d33f0" />

Now these values can be picked up with JavaScript and send as AJAX POST request which remains transparent for final user:

$(document).ready(function() {
  var token = $("meta[name='_csrf']").attr("content");
  if (!token) token = "";
  var header = $("meta[name='_csrf_header']").attr("content");
  if (!header) header = "";

  $('.do-logout').click(function() {
    $.ajax({
      type:"POST",
      beforeSend: function (request) {
        request.setRequestHeader(header, token);
      },
      url: "/logout",
      success: function(msg) {
        // supplementary checks should be done here, but because of readability
        // we'll prefer shorter version
        window.location.href = "http://localhost:8080/login";
      },
      error: function() {
        alert("FAILURE !");
      }
    }); 
    return false;
  });

});

In this article we learned how CSRF protection is implemented in Spring Security. At the first part we discovered the definition of CSRF as a request executed unintentionally by final user. The second part presented the protection used by Spring Security. We saw once again that the concepts of filter and repository were used. In the last part we showed how to implement CSRF protection in XML-based configuration.

Note also that by adding <csrf /> inside <http /> configuration you desactivated the logout through GET request. Since now, the logout will only be possible with POST method. This override is made with org.springframework.security.config.http.AuthenticationConfigBuilder. It contains a private field called csrfLogoutHandler. This field represent the bean definition for CsrfLogoutHandler class. The definition is returned in BeanDefinition getCsrfLogoutHandler() method of org.springframework.security.config.http.CsrfBeanDefinitionParser class. If it doesn't return null, builder for <http /> considers that CSRF protection is active. In this case we consider that CSRF token need to be removed from user's session repository. New CSRF token will be generated for new user session. Code from CsrfLogoutHandler, in charge of clean repository, is:

public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
  this.csrfTokenRepository.saveToken(null, request, response);
}

It will remove CSRF entry from current user session. However imagine that your client doesn't want to handle logout in with visual forms. We can change it in several steps. First of all, we need to add AJAX request which will call /logout URL in POST. Secondly, this request must contain either _csrf parameter or CSRF header. Because we've already discovered the first solution, we'll opt for the second one. To get header attributes, we'll use <sec:csrfMetaTags /> tag which should generate something as:

<meta name="_csrf_parameter" content="_csrf" />
<meta name="_csrf_header" content="X-CSRF-TOKEN" />
<meta name="_csrf" content="97950459-f9aa-4b29-9179-d702228d33f0" />

Now these values can be picked up with JavaScript and send as AJAX POST request which remains transparent for final user:

$(document).ready(function() {
  var token = $("meta[name='_csrf']").attr("content");
  if (!token) token = "";
  var header = $("meta[name='_csrf_header']").attr("content");
  if (!header) header = "";

  $('.do-logout').click(function() {
    $.ajax({
      type:"POST",
      beforeSend: function (request) {
        request.setRequestHeader(header, token);
      },
      url: "/logout",
      success: function(msg) {
        // supplementary checks should be done here, but because of readability
        // we'll prefer shorter version
        window.location.href = "http://localhost:8080/login";
      },
      error: function() {
        alert("FAILURE !");
      }
    }); 
    return false;
  });

});

In this article we learned how CSRF protection is implemented in Spring Security. At the first part we discovered the definition of CSRF as a request executed unintentionally by final user. The second part presented the protection used by Spring Security. We saw once again that the concepts of filter and repository were used. In the last part we showed how to implement CSRF protection in XML-based configuration.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!