CSRF Protection with Spring MVC and Thymeleaf

1. Introduction

Thymeleaf is a Java template engine for processing and creating HTML, XML, JavaScript, CSS and plaintext. For an intro to Thymeleaf and Spring, have a look at this writeup.

In this article, we will discuss how to prevent Cross-Site Request Forgery (CSRF) attacks in Spring MVC with Thymeleaf application. To be more specific, we will test CSRF attack for HTTP POST method.

CSRF is an attack which forces an end user to execute unwanted actions in a web application in which is currently authenticated.

2. Maven Dependencies

First, let us see the configurations required to integrate Thymeleaf with Spring. The thymeleaf-spring library is required in our dependencies:

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.9.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring4</artifactId>
    <version>3.0.9.RELEASE</version>
</dependency>

Note that, for a Spring 4 project, the thymeleaf-spring4 library must be used instead of thymeleaf-spring5. The latest version of the dependencies may be found here.

Moreover, in order to use Spring Security, we need to add following dependencies:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>

The latest versions of two Spring Security related libraries are available here and here.

3. Java Configuration

In addition to Thymeleaf configuration covered here, we need to add configuration for Spring Security. In order to do that, we need to create the class:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebMVCSecurity extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
          .withUser("user1").password("{noop}user1Pass")
          .authorities("ROLE_USER");
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/resources/**");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .authorizeRequests()
          .anyRequest()
          .authenticated()
          .and()
          .httpBasic();
    }
}

For more details and description of Security configuration, we refer to the Security with Spring series.

CSRF protection is enabled by default with Java configuration. In order to disable this useful feature we need to add this in configure(…) method:

.csrf().disable()

In XML configuration we need to specify the CSRF protection manually, otherwise, it will not work:

<security:http
  auto-config="true"
  disable-url-rewriting="true"
  use-expressions="true">
    <security:csrf />

    <!-- Remaining configuration ... -->
</security:http>

Please also note, that if we are using login page with login form, we need to always include the CSRF token in the login form as a hidden parameter manually in the code:

<input
  type="hidden"
  th:name="${_csrf.parameterName}"
  th:value="${_csrf.token}" />

For the remaining forms, CSRF token will be automatically added to forms with hidden input:

<input
  type="hidden"
  name="_csrf"
  value="32e9ae18-76b9-4330-a8b6-08721283d048" />
<!-- Example token -->

4. Views Configuration

Let’s proceed to the main part of HTML files with form actions and testing procedure creation. In the first view, we try to add new student to the list:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">
<head>
<title>Add Student</title>
</head>
<body>
    <h1>Add Student</h1>
        <form action="#" th:action="@{/saveStudent}" th:object="${student}"
          method="post">
            <ul>
                <li th:errors="*{id}" />
                <li th:errors="*{name}" />
                <li th:errors="*{gender}" />
                <li th:errors="*{percentage}" />
            </ul>
    <!-- Remaining part of HTML -->
    </form>
</body>
</html>

In this view, we are adding a student to the list, by providing id, name, gender and percentage (optionally, as stated in the form validation). Before we can execute this form, we need to provide user and password, to authenticate us in a web application.

4.1. Browser CSRF Attack Testing

Now we proceed to the second HTML view. The purpose of it is to try to do CSRF attack:

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<form action="http://localhost:8080/spring-thymeleaf/saveStudent" method="post">
    <input type="hidden" name="payload" value="CSRF attack!"/>
    <input type="submit" />
</form>
</body>
</html>

We know that the action URL is http://localhost:8080/spring-thymeleaf/saveStudent. The hacker wants to access this page to perform an attack.

In order to test, open the HTML file in another browser, without logging in to the application. When you try to submit the form, we will receive the page:

image

 

Our request was denied because we sent a request without a CSRF token.

Please note, that HTTP session is used in order to store CSRF token. When the request is sent, Spring compares generated token with the token stored in the session, in order to confirm that the user is not hacked.

4.2. JUnit CSRF Attack Testing

If you don’t want to test CSRF attack using a browser, you can also do it via a quick integration test; let’s start with the Spring config for that test:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {
  WebApp.class, WebMVCConfig.class, WebMVCSecurity.class, InitSecurity.class })
public class CsrfEnabledIntegrationTest {

    // configuration

}

And move on to the actual tests:

@Test
public void addStudentWithoutCSRF() throws Exception {
    mockMvc.perform(post("/saveStudent").contentType(MediaType.APPLICATION_JSON)
      .param("id", "1234567").param("name", "Joe").param("gender", "M")
      .with(testUser())).andExpect(status().isForbidden());
}

@Test
public void addStudentWithCSRF() throws Exception {
    mockMvc.perform(post("/saveStudent").contentType(MediaType.APPLICATION_JSON)
      .param("id", "1234567").param("name", "Joe").param("gender", "M")
      .with(testUser()).with(csrf())).andExpect(status().isOk());
}

The first test will result in a forbidden status due to the missing CSRF token, whereas the second will be executed properly.

5. Conclusion

In this article, we discussed how to prevent CSRF attacks using Spring Security and Thymeleaf framework.

The full implementation of this tutorial can be found in the GitHub project – this is an Eclipse based project, so it should be easy to import and run as it is.

Leave a Reply

Your email address will not be published.