28
votes

Recently we have introduced CSRF protection for our project which uses spring security 3.2.

After enabling CSRF some of the unit tests are failing because of the csrf token is not present in request. I put some dummy value into '_csrf' parameter and it didn't work.

Is there anyway that I can get the csrf token before sending the request (when unit testing)?

3

3 Answers

53
votes

The way to solve this issue is :

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"));
}

The important part is : .with(csrf()) which will add the expected _csrf parameter to the query.

The csrf() static method is provided by spring-security-test :

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

Your unit test will require the following import to access it:

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

I have found a work around to fix this issue by creating a custom CsrfTokenRepository implementation. This will always generate a constant token (like "test_csrf_token"). So we can send that token as a request parameter (since it'll not change) with other form parameters. Here are the steps I followed to resolve my issue.

  1. create a class implementing CsrfTokenRepository interface. Implement generate token with some constant token value.

    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. Add reference to csrf tag in your security configuration.

    <http>
       <csrf token-repository-ref="customCsrfTokenRepository" />
       ....
    </http>
    
    <beans:bean id="customCsrfTokenRepository" class="com.portal.controller.security.TestCsrfTokenRepository"></beans:bean>
    
  3. Modify your test cases by adding csrf request parameter.

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

In addition to @Thierry answer there is similar solution for reactive stack.

When calling your backend withWebTestClient:

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

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