提交 7ae44c25 编写于 作者: B Brian Clozel

Allow skipping JSON prefix in MockMvc result matchers

JSON payloads are sometimes prepended with a static string prefix
to prevent Cross Site Scripting Inclusion attacks (XSSI).
Prior to this commit, doing so would fail the MockMvc
`JsonPathResultMatchers` since they're considering the whole response as
the JSON payload.

This commit adds a new `JsonPathResultMatchers.prefix` method that
configures the matchers to check for the presence of that string (i.e.
fail if it's not there) and only consider the rest of the response body
as the JSON payload for other assertions.

Issue: SPR-13577
上级 4a6c2dbb
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -16,12 +16,17 @@
package org.springframework.test.web.servlet.result;
import java.io.UnsupportedEncodingException;
import com.jayway.jsonpath.JsonPath;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.hamcrest.core.StringStartsWith;
import org.springframework.test.util.JsonPathExpectationsHelper;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
import org.springframework.util.StringUtils;
/**
* Factory for assertions on the response content using
......@@ -33,12 +38,14 @@ import org.springframework.test.web.servlet.ResultMatcher;
* @author Rossen Stoyanchev
* @author Craig Andrews
* @author Sam Brannen
* @author Brian Clozel
* @since 3.2
*/
public class JsonPathResultMatchers {
private final JsonPathExpectationsHelper jsonPathHelper;
private String prefix;
/**
* Protected constructor.
......@@ -48,10 +55,23 @@ public class JsonPathResultMatchers {
* @param args arguments to parameterize the {@code JsonPath} expression with,
* using formatting specifiers defined in {@link String#format(String, Object...)}
*/
protected JsonPathResultMatchers(String expression, Object ... args) {
protected JsonPathResultMatchers(String expression, Object... args) {
this.jsonPathHelper = new JsonPathExpectationsHelper(expression, args);
}
/**
* Configures the current {@code JsonPathResultMatchers} instance
* to verify that the JSON payload is prepended with the given prefix.
* <p>Use this method if the JSON payloads are prefixed to avoid
* Cross Site Script Inclusion (XSSI) attacks.
* @param prefix the string prefix prepended to the actual JSON payload
* @since 4.3.0
*/
public JsonPathResultMatchers prefix(String prefix) {
this.prefix = prefix;
return this;
}
/**
* Evaluate the JSON path expression against the response content and
......@@ -61,7 +81,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValue(content, matcher);
}
};
......@@ -75,7 +95,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
jsonPathHelper.assertValue(result.getResponse().getContentAsString(), expectedValue);
jsonPathHelper.assertValue(getContent(result), expectedValue);
}
};
}
......@@ -91,7 +111,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.exists(content);
}
};
......@@ -108,7 +128,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.doesNotExist(content);
}
};
......@@ -128,7 +148,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValueIsEmpty(content);
}
};
......@@ -148,7 +168,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValueIsNotEmpty(content);
}
};
......@@ -163,7 +183,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValueIsString(content);
}
};
......@@ -178,7 +198,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValueIsBoolean(content);
}
};
......@@ -193,7 +213,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValueIsNumber(content);
}
};
......@@ -207,7 +227,7 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValueIsArray(content);
}
};
......@@ -222,10 +242,29 @@ public class JsonPathResultMatchers {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
String content = getContent(result);
jsonPathHelper.assertValueIsMap(content);
}
};
}
private String getContent(MvcResult result) throws UnsupportedEncodingException {
String content = result.getResponse().getContentAsString();
if (StringUtils.hasLength(this.prefix)) {
try {
String reason = String.format("Expected a JSON payload prefixed with \"%s\" but found: %s", this.prefix,
StringUtils.quote(content.substring(0, this.prefix.length())));
MatcherAssert.assertThat(reason, content, StringStartsWith.startsWith(this.prefix));
return content.substring(this.prefix.length());
}
catch (StringIndexOutOfBoundsException oobe) {
String message = "JSON prefix \"" + this.prefix + "\" not found, exception: ";
throw new AssertionError(message + oobe.getMessage());
}
}
else {
return content;
}
}
}
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......
/*
* Copyright 2002-2015 the original author or authors.
* Copyright 2002-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
......@@ -29,6 +29,7 @@ import org.springframework.test.web.servlet.StubMvcResult;
* @author Rossen Stoyanchev
* @author Craig Andrews
* @author Sam Brannen
* @author Brian Clozel
*/
public class JsonPathResultMatchersTests {
......@@ -41,7 +42,7 @@ public class JsonPathResultMatchersTests {
"'emptyString': '', " + //
"'emptyArray': [], " + //
"'emptyMap': {} " + //
"}";
"}";
private static final StubMvcResult stubMvcResult;
......@@ -57,7 +58,6 @@ public class JsonPathResultMatchersTests {
}
}
@Test
public void value() throws Exception {
new JsonPathResultMatchers("$.str").value("foo").match(stubMvcResult);
......@@ -233,4 +233,42 @@ public class JsonPathResultMatchersTests {
new JsonPathResultMatchers("$.arr").isString().match(stubMvcResult);
}
@Test(expected = AssertionError.class)
public void valueWithJsonPrefixNotConfigured() throws Exception {
String jsonPrefix = "prefix";
StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
new JsonPathResultMatchers("$.str").value("foo").match(result);
}
@Test(expected = AssertionError.class)
public void valueWithJsonWrongPrefix() throws Exception {
String jsonPrefix = "prefix";
StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
new JsonPathResultMatchers("$.str").prefix("wrong").value("foo").match(result);
}
@Test
public void valueWithJsonPrefix() throws Exception {
String jsonPrefix = "prefix";
StubMvcResult result = createPrefixedStubMvcResult(jsonPrefix);
new JsonPathResultMatchers("$.str").prefix(jsonPrefix).value("foo").match(result);
}
@Test(expected = AssertionError.class)
public void prefixWithPayloadNotLongEnough() throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse();
response.addHeader("Content-Type", "application/json");
response.getWriter().print(new String("test".getBytes("ISO-8859-1")));
StubMvcResult result = new StubMvcResult(null, null, null, null, null, null, response);
new JsonPathResultMatchers("$.str").prefix("prefix").value("foo").match(result);
}
private StubMvcResult createPrefixedStubMvcResult(String jsonPrefix) throws Exception {
MockHttpServletResponse response = new MockHttpServletResponse();
response.addHeader("Content-Type", "application/json");
response.getWriter().print(jsonPrefix + new String(RESPONSE_CONTENT.getBytes("ISO-8859-1")));
return new StubMvcResult(null, null, null, null, null, null, response);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册