Контроллеры тестирования модулей с защитой CSRF включены в весеннюю безопасность

Недавно мы внедрили CSRF-защиту для нашего проекта, который использует весеннюю безопасность 3.2.

После включения CSRF некоторые из модульных тестов терпят неудачу из-за того, что токен csrf отсутствует в запросе. Я поместил некоторое фиктивное значение в параметр _csrf, и это не сработало.

Есть ли в любом случае, что я могу получить токен csrf перед отправкой запроса (при модульном тестировании)?

Ответ 1

Ваш ответ uiroshan побеждает назначение токена csrf: в вашей конфигурации это будет постоянное значение (если ваша конфигурация не используется только в тестовом контексте, но вы ее не указали).

Правильный (и более простой) способ решения этой проблемы:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;

...

@Test
public void testLogin() throws Exception {
    this.mockMvc.perform(post("/login")
            .param("username", "...")
            .param("password", "...")
            .with(csrf()))
        .andExpect(status().isFound())
        .andExpect(header().string("Location", "redirect-url-on-success-login"));
}

Важная часть: .with(csrf()), которая добавит ожидаемый параметр _csrf в запрос.

Статический метод csrf() предоставляется spring-security-test:

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <version>4.2.13.RELEASE / 5.1.6.RELEASE</version>
    <scope>test</scope>
</dependency>

Для вашего модульного теста потребуется следующий импорт:

 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;

Ответ 2

Я нашел работу, чтобы исправить эту проблему, создав собственную реализацию CsrfTokenRepository. Это всегда будет генерировать постоянный токен (например, test_csrf_token). Таким образом, мы можем отправить этот токен в качестве параметра запроса (поскольку он не изменится) с другими параметрами формы. Ниже приведены шаги, которые я предпринял для решения моей проблемы.

  1. создать класс, реализующий интерфейс CsrfTokenRepository. Реализовать генерировать токен с некоторым постоянным значением токена.

    public CsrfToken generateToken(HttpServletRequest request) {
       return new DefaultCsrfToken(headerName, parameterName, "test_csrf_token");
    }
    
    @Override
    public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) {
        if (token == null) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                session.removeAttribute(sessionAttributeName);
            }
        } else {
            HttpSession session = request.getSession();
            session.setAttribute(sessionAttributeName, token);
        }
     }
    
     @Override
     public CsrfToken loadToken(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null) {
           return null;
        }
        return (CsrfToken) session.getAttribute(sessionAttributeName);
     }
    
  2. Добавьте ссылку на тег csrf в своей конфигурации безопасности.

    <http>
       <csrf token-repository-ref="customCsrfTokenRepository" />
       ....
    </http>
    
    <beans:bean id="customCsrfTokenRepository" class="com.portal.controller.security.TestCsrfTokenRepository"></beans:bean>
    
  3. Измените свои тестовые примеры, добавив параметр запроса csrf.

    request.addParameter("_csrf", "test_csrf_token");
    

Ответ 3

В дополнение к ответу @Thierry существует аналогичное решение для реактивного стека.

При вызове вашего бэкэнда с помощью WebTestClient:

import org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf

        // ...
        webTestClient.mutateWith(csrf()).post()...