34

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)?

Manuel Jordan
  • 15,253
  • 21
  • 95
  • 158
uiroshan
  • 5,021
  • 2
  • 39
  • 37

3 Answers3

63

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.*;
Thierry
  • 5,270
  • 33
  • 39
  • 1
    @WannyMiarelli : this answer do not depend on the body content type. Perhap's your security configuration has a custom behavior for json content type requests ? – Thierry Nov 02 '19 at 07:30
  • yes i know - but i was searching for using CSRF with the X-CSRF-TOKEN header and it was as simple as appending the .asHeader() function – Wanny Miarelli Nov 02 '19 at 17:59
  • 1
    After wasting so much time I found this answer. csrf() function was the key. Only 1 tumbs up is not enough, I want to give 100 thumbs up to this answer – Junaed Sep 14 '20 at 12:26
  • I am getting `org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: No primary or single unique constructor found for interface org.springframework.security.web.csrf.CsrfToken` with `spring-security-test` version 5.6.10 – rMonteiro Apr 13 '23 at 17:00
3

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");
    
uiroshan
  • 5,021
  • 2
  • 39
  • 37
2

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()...
pixel
  • 24,905
  • 36
  • 149
  • 251