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