CSRF Protection with Spring MVC and Thymeleaf – 用Spring MVC和Thymeleaf进行CSRF保护

最后修改: 2016年 9月 26日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

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.

Thymeleaf是一个Java模板引擎,用于处理和创建HTML、XML、JavaScript、CSS和纯文本。关于Thymeleaf和Spring的介绍,请看这篇报道

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.

在这篇文章中,我们将讨论如何在Spring MVC与Thymeleaf应用程序中防止跨站请求伪造(CSRF)攻击。更具体地说,我们将测试HTTP POST方法的CSRF攻击。

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

CSRF是一种攻击,它迫使终端用户在一个目前已被验证的网络应用程序中执行不需要的操作。

2. Maven Dependencies

2.Maven的依赖性

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

首先,让我们看看整合Thymeleaf和Spring所需的配置。在我们的依赖中需要thymeleaf-spring库。

<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf</artifactId>
    <version>3.0.11.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
    <version>3.0.11.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.

请注意,对于Spring 4项目,必须使用thymeleaf-spring4库而不是thymeleaf-spring5。最新版本的依赖项可以在这里找到。

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

此外,为了使用Spring Security,我们需要添加以下依赖项。

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

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

两个与Spring Security相关的库的最新版本可在这里这里获得。

3. Java Configuration

3.Java配置[/strong]

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

除了涵盖这里的Thymeleaf配置外,我们还需要为Spring Security添加配置。为了做到这一点,我们需要创建一个类。

@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.

关于安全配置的更多细节和描述,我们参考了Security with Spring系列。

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保护在Java配置中是默认启用的。为了禁用这个有用的功能,我们需要在configure(..)方法中添加这个。

.csrf().disable()

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

在XML配置中,我们需要手动指定CSRF保护,否则,它将无法工作。

<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:

还请注意,如果我们使用带有登录表单的登录页面,我们需要在登录表单中始终将CSRF令牌作为一个隐藏的参数手动写入代码。

<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:

对于其余的表单,CSRF令牌将被自动添加到有隐藏输入的表单中。

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

4. Views Configuration

4.视图配置

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:

让我们进入HTML文件的主要部分,进行表单操作和测试程序的创建。在第一个视图中,我们尝试在列表中添加新的学生。

<!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.

在这个视图中,我们通过提供idnamegenderpercentage(可选择,如表格验证中所述),将一个学生添加到列表中。在执行这个表单之前,我们需要提供userpassword,以便在网络应用中验证我们。

4.1. Browser CSRF Attack Testing

4.1.浏览器CSRF攻击测试

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

现在我们进入第二个HTML视图。它的目的是尝试进行CSRF攻击。

<!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.

我们知道,行动的URL是http://localhost:8080/spring-thymeleaf/saveStudent。黑客想要访问这个页面来进行攻击。

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:

为了测试,在另一个浏览器中打开HTML文件,不要登录到应用程序。当你试图提交表格时,我们将收到该页面。

Zrzut-ekranu

 

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

我们的请求被拒绝,因为我们发送的请求没有CSRF令牌。

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.

请注意,HTTP会话是用来存储CSRF令牌的。当请求被发送时,Spring将生成的令牌与存储在会话中的令牌进行比较,以确认用户没有被入侵。

4.2. JUnit CSRF Attack Testing

4.2.JUnit CSRF攻击测试

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:

如果你不想用浏览器来测试CSRF攻击,你也可以通过快速集成测试来进行;让我们从该测试的Spring配置开始。

@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.

第一个测试将由于CSRF标记的缺失而导致禁止状态,而第二个测试将被正常执行。

5. Conclusion

5.结论

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

在这篇文章中,我们讨论了如何使用Spring Security和Thymeleaf框架来防止CSRF攻击。

The full implementation of this tutorial can be found in the GitHub project.

本教程的完整实现可以在GitHub项目中找到。