user1408140
user1408140

Reputation: 667

How to access a CSRF token in a Spring controller unit test?

I have a Spring controller that upon an authenticated GET from "/user" returns the following JSON:

{"name":<name>,"token":<csrf-token>}

I tried to construct a unit test for the controller that will verify that the returned JSON contains a dynamically-generated CSRF token:

@Autowired
private FilterChainProxy springSecurityFilterChain;

private MockMvc mvc;

@Before
public void setUp()
    throws Exception
{
    ...
    mvc = standaloneSetup(controller)
        .apply(springSecurity(springSecurityFilterChain))
        .build();
}

@Test
public void getUser()
    throws Exception
{
    CsrfRequestPostProcessor csrfPostProcessor = null;
    mvc.perform(get("/user").with(user(Const.USER)).with(csrfPostProcessor = csrf()))
        .andExpect(status().isOk())
        .andExpect(content().json("{\"name\":\"" + Const.NAME + "\",\"token\":\"" + csrfPostProcessor.toString() + "\"}"));
}

The test fails along these lines:

Failed tests: 
  ControllerTest.getUser:74 token
Expected: org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors$CsrfRequestPostProcessor@203c20cf
     got: 565a95b0-d0bb-4376-a8a0-725a3b16a787

Is there any way to fix this and if not, is there a way to construct an alternative test that will use a dynamically-generated CSRF token?

Upvotes: 3

Views: 1194

Answers (1)

user1408140
user1408140

Reputation: 667

Here is one possible solution -- construct a custom matcher that intercepts the CSRF token in UUID4 format:

private static Pattern CSRF_PATTERN = Pattern.compile("[\\da-f]{8}-[\\da-f]{4}-4[\\da-f]{3}-[\\da-f]{4}-[\\da-f]{12}");

public static Matcher<String> isCsrf(StringBuilder intercept) {
    return new ArgumentMatcher<String>() {
        @Override
        public boolean matches(Object obj) {
            // intercept may be null
            Assert.isTrue(obj instanceof DefaultCsrfToken, "obj");

            String token = ((DefaultCsrfToken)obj).getToken();
            if (intercept != null) {
                intercept.setLength(0);
                intercept.append(token);
            }

            java.util.regex.Matcher matcher = CSRF_PATTERN.matcher(token);
            return matcher.matches();
        }           
    };
}

Then use it as follows:

@Test
public void getUser()
    throws Exception
{
    StringBuilder token = new StringBuilder();
    mvc.perform(get("/user").with(user(Const.USER)).with(csrf()))
        .andExpect(status().isOk())
        .andExpect(request().attribute("_csrf", isCsrf(csrf)))
        .andExpect(content().json("{\"name\":\"" + Const.NAME + "\",\"token\":\"" + token.toString() + "\"}"));
}

As a bonus, you get a CSRF token validator.

Upvotes: 1

Related Questions