Перейти к основному содержимому

Защита от CSRF с помощью Spring MVC и Thymeleaf

· 4 мин. чтения

1. Введение

Thymeleaf — это механизм шаблонов Java для обработки и создания HTML, XML, JavaScript, CSS и открытого текста. Чтобы познакомиться с Thymeleaf и Spring, ознакомьтесь с этой статьей .

В этой статье мы обсудим, как предотвратить атаки с подделкой межсайтовых запросов (CSRF) в Spring MVC с помощью приложения Thymeleaf. Чтобы быть более конкретным, мы протестируем атаку CSRF для метода HTTP POST.

CSRF — это атака, которая вынуждает конечного пользователя выполнять нежелательные действия в веб-приложении, в котором в данный момент проходит аутентификация.

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 вместо тимелеаф-спринг5 необходимо использовать библиотеку тимелеаф-спринг4 . Последнюю версию зависимостей можно найти здесь .

Более того, чтобы использовать 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>

В этом представлении мы добавляем студента в список, предоставляя идентификатор , имя , пол и процент (необязательно, как указано в проверке формы). Прежде чем мы сможем выполнить эту форму, нам нужно указать пользователя и пароль для аутентификации в веб-приложении.

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-файл в другом браузере, не входя в приложение. При попытке отправить форму мы получим страницу:

./903621539729b806e3128a7152b2bdad.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, тогда как второй будет выполнен правильно.

5. Вывод

В этой статье мы обсудили, как предотвратить атаки CSRF с помощью Spring Security и платформы Thymeleaf.

Полную реализацию этого туториала можно найти в проекте GitHub .