Contents

使用 Spring Security 和 Thymeleaf 框架来防止 CSRF 攻击

1. 简介

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

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

CSRF 是一种攻击,它迫使最终用户在当前经过身份验证的 Web 应用程序中执行不需要的操作。

2. Maven依赖

首先,让我们看看将 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>

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

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

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

两个 Spring Security 相关库的最新版本可在此处此处 获得。

3. Java配置

除了这里 介绍的 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();
    }
}

有关安全配置的更多详细信息和说明,请参阅使用 Spring 的安全 系列。

**CSRF 保护在 Java 配置中默认启用。**为了禁用这个有用的功能,我们需要在*configure(…)*方法中添加它:

.csrf().disable()

在 XML 配置中我们需要手动指定 CSRF 保护,否则将不起作用:

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

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

另请注意,如果我们使用带有登录表单的登录页面,我们需要始终将登录表单中的 CSRF 令牌作为隐藏参数手动包含在代码中:

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

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

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

4. 视图配置

让我们继续 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>

在此视图中,我们通过提供idnamegenderpercentage(可选,如表单验证中所述)将学生添加到列表中。在执行此表单之前,我们需要提供userpassword,以便在 Web 应用程序中对我们进行身份验证。

4.1.浏览器 CSRF 攻击测试

现在我们进入第二个 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>

我们知道操作 URL 是http://localhost:8080/spring-thymeleaf/saveStudent 。黑客想要访问此页面以执行攻击。

为了进行测试,请在另一个浏览器中打开 HTML 文件,而无需登录应用程序。当您尝试提交表单时,我们将收到以下页面:

/uploads/csrf_thymeleaf_with_spring_security/1.png

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

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

4.2. JUnit CSRF 攻击测试

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

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

并继续进行实际测试:

@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());
}

由于缺少 CSRF 令牌,第一个测试将导致禁止状态,而第二个将正确执行。