mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Add trusted packages to MappingJackson2MessageConverter
This commit introduces trusted packages, specified via the related setter for untrusted use cases. It allows explicit configuration of which Java packages are allowed to be deserialized. Closes gh-36791
This commit is contained in:
committed by
Brian Clozel
parent
fe68e337b2
commit
9bec52b1ec
+37
@@ -21,6 +21,7 @@ import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -49,6 +50,8 @@ import org.springframework.util.ClassUtils;
|
||||
* {@link #setTargetType targetType} is set to {@link MessageType#TEXT}.
|
||||
* Converts from a {@link TextMessage} or {@link BytesMessage} to an object.
|
||||
*
|
||||
* <p>For untrusted environments, use {@link #setTrustedPackages(String...)}.
|
||||
*
|
||||
* <p>It customizes Jackson's default properties with the following ones:
|
||||
* <ul>
|
||||
* <li>{@link MapperFeature#DEFAULT_VIEW_INCLUSION} is disabled</li>
|
||||
@@ -59,6 +62,7 @@ import org.springframework.util.ClassUtils;
|
||||
* @author Dave Syer
|
||||
* @author Juergen Hoeller
|
||||
* @author Stephane Nicoll
|
||||
* @author Sebastien Deleuze
|
||||
* @since 3.1.4
|
||||
* @deprecated since 7.0 in favor of {@link JacksonJsonMessageConverter}
|
||||
*/
|
||||
@@ -85,11 +89,14 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
|
||||
|
||||
private final Map<Class<?>, String> classIdMappings = new HashMap<>();
|
||||
|
||||
private String @Nullable [] trustedPackages;
|
||||
|
||||
private @Nullable ClassLoader beanClassLoader;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a {@code MappingJackson2MessageConverter} with a default {@link ObjectMapper}.
|
||||
* @see #setTrustedPackages(String...)
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // on Jackson 2.13: configure(MapperFeature, boolean)
|
||||
public MappingJackson2MessageConverter() {
|
||||
@@ -102,12 +109,21 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
|
||||
* Construct a {@code MappingJackson2MessageConverter} with a custom {@link ObjectMapper}.
|
||||
* @param objectMapper the {@code ObjectMapper} to use
|
||||
* @since 6.1
|
||||
* @see #setTrustedPackages(String...)
|
||||
*/
|
||||
public MappingJackson2MessageConverter(ObjectMapper objectMapper) {
|
||||
Assert.notNull(objectMapper, "ObjectMapper must not be null");
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify the trusted Java packages for deserialization.
|
||||
* @param trustedPackages the trusted Java packages for deserialization
|
||||
* @since 6.2.19
|
||||
*/
|
||||
public void setTrustedPackages(String... trustedPackages) {
|
||||
this.trustedPackages = trustedPackages.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@code ObjectMapper} for this converter.
|
||||
@@ -181,6 +197,23 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isTrustedPackage(String requestedType) {
|
||||
if (this.trustedPackages != null) {
|
||||
String packageName = ClassUtils.getPackageName(requestedType);
|
||||
int lastBracketIndex = packageName.lastIndexOf('[');
|
||||
if (lastBracketIndex != -1 && packageName.length() > lastBracketIndex + 1 && packageName.charAt(lastBracketIndex + 1) == 'L') {
|
||||
packageName = packageName.substring(lastBracketIndex + 2);
|
||||
}
|
||||
for (String trustedPackage : this.trustedPackages) {
|
||||
if (packageName.equals(trustedPackage)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setBeanClassLoader(ClassLoader classLoader) {
|
||||
this.beanClassLoader = classLoader;
|
||||
@@ -461,6 +494,10 @@ public class MappingJackson2MessageConverter implements SmartMessageConverter, B
|
||||
if (mappedClass != null) {
|
||||
return this.objectMapper.constructType(mappedClass);
|
||||
}
|
||||
if (!isTrustedPackage(typeId)) {
|
||||
throw new MessageConversionException("The class '" + typeId + "' is not in the trusted packages: " +
|
||||
Arrays.toString(this.trustedPackages));
|
||||
}
|
||||
try {
|
||||
Class<?> typeClass = ClassUtils.forName(typeId, this.beanClassLoader);
|
||||
return this.objectMapper.constructType(typeClass);
|
||||
|
||||
+82
@@ -37,6 +37,7 @@ import org.mockito.stubbing.Answer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.isA;
|
||||
@@ -48,6 +49,7 @@ import static org.mockito.Mockito.verify;
|
||||
* @author Arjen Poutsma
|
||||
* @author Dave Syer
|
||||
* @author Stephane Nicoll
|
||||
* @author Sebastien Deleuze
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
class MappingJackson2MessageConverterTests {
|
||||
@@ -134,6 +136,86 @@ class MappingJackson2MessageConverterTests {
|
||||
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromTextMessageUntrusted() throws Exception {
|
||||
converter = new MappingJackson2MessageConverter();
|
||||
converter.setTrustedPackages("java.lang");
|
||||
converter.setTypeIdPropertyName("__typeid__");
|
||||
TextMessage textMessageMock = mock();
|
||||
|
||||
String text = "{\"foo\":\"bar\"}";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
assertThatExceptionOfType(MessageConversionException.class)
|
||||
.isThrownBy(() -> converter.fromMessage(textMessageMock))
|
||||
.withMessageContaining("is not in the trusted packages");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromTextMessageTrusted() throws Exception {
|
||||
converter = new MappingJackson2MessageConverter();
|
||||
converter.setTrustedPackages("java.lang", "org.springframework.jms.support.converter");
|
||||
converter.setTypeIdPropertyName("__typeid__");
|
||||
TextMessage textMessageMock = mock();
|
||||
MyBean unmarshalled = new MyBean("bar");
|
||||
|
||||
String text = "{\"foo\":\"bar\"}";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
MyBean result = (MyBean) converter.fromMessage(textMessageMock);
|
||||
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromTextMessageTrustedEmpty() throws Exception {
|
||||
converter = new MappingJackson2MessageConverter();
|
||||
converter.setTrustedPackages();
|
||||
converter.setTypeIdPropertyName("__typeid__");
|
||||
TextMessage textMessageMock = mock();
|
||||
|
||||
String text = "{\"foo\":\"bar\"}";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn(MyBean.class.getName());
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
assertThatExceptionOfType(MessageConversionException.class)
|
||||
.isThrownBy(() -> converter.fromMessage(textMessageMock))
|
||||
.withMessageContaining("is not in the trusted packages");
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromTextMessageTrusted1DArray() throws Exception {
|
||||
converter = new MappingJackson2MessageConverter();
|
||||
converter.setTrustedPackages("org.springframework.jms.support.converter");
|
||||
converter.setTypeIdPropertyName("__typeid__");
|
||||
TextMessage textMessageMock = mock();
|
||||
MyBean[] unmarshalled = new MyBean[] { new MyBean("bar") };
|
||||
|
||||
String text = "[{\"foo\":\"bar\"}]";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn("[L" + MyBean.class.getName() + ";");
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
MyBean[] result = (MyBean[]) converter.fromMessage(textMessageMock);
|
||||
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromTextMessageTrusted2DArray() throws Exception {
|
||||
converter = new MappingJackson2MessageConverter();
|
||||
converter.setTrustedPackages("org.springframework.jms.support.converter");
|
||||
converter.setTypeIdPropertyName("__typeid__");
|
||||
TextMessage textMessageMock = mock();
|
||||
MyBean[][] unmarshalled = new MyBean[][] { { new MyBean("bar") } };
|
||||
|
||||
String text = "[[{\"foo\":\"bar\"}]]";
|
||||
given(textMessageMock.getStringProperty("__typeid__")).willReturn("[[L" + MyBean.class.getName() + ";");
|
||||
given(textMessageMock.getText()).willReturn(text);
|
||||
|
||||
MyBean[][] result = (MyBean[][]) converter.fromMessage(textMessageMock);
|
||||
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
void fromTextMessageWithUnknownProperty() throws Exception {
|
||||
TextMessage textMessageMock = mock();
|
||||
|
||||
Reference in New Issue
Block a user