Reputation: 667
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
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