Как получить доступ к токену CSRF в тесте модулей контроллера Spring?

У меня есть контроллер Spring, который после проверки подлинности GET из "/user" возвращает следующий JSON:

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

Я попытался создать модульный тест для контроллера, который проверит, что возвращаемый JSON содержит динамически сгенерированный токен CSRF:

@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() + "\"}"));
}

Тест не проходит по следующим направлениям:

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

Есть ли способ исправить это, и если нет, то есть ли способ создать альтернативный тест, который будет использовать динамически генерируемый токен CSRF?

1 ответ

Вот одно из возможных решений - создать собственный сопоставитель, который перехватывает токен CSRF в формате UUID4:

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

Затем используйте его следующим образом:

@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() + "\"}"));
}

В качестве бонуса вы получите валидатор токена CSRF.

Другие вопросы по тегам