diff --git a/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java b/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java
index a8a70006672..bb9f5f1977a 100644
--- a/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java
+++ b/spring-jms/src/main/java/org/springframework/jms/support/converter/JacksonJsonMessageConverter.java
@@ -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;
@@ -48,6 +49,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.
*
+ *
For untrusted environments, use {@link #setTrustedPackages(String...)}.
+ *
* @author Sebastien Deleuze
* @since 7.0
*/
@@ -73,6 +76,8 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
private final Map, String> classIdMappings = new HashMap<>();
+ private String @Nullable [] trustedPackages;
+
private @Nullable ClassLoader beanClassLoader;
@@ -80,6 +85,7 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
* Construct a new instance with a {@link JsonMapper} customized with the
* {@link tools.jackson.databind.JacksonModule}s found by
* {@link MapperBuilder#findModules(ClassLoader)}.
+ * @see #setTrustedPackages(String...)
*/
public JacksonJsonMessageConverter() {
this(JsonMapper.builder());
@@ -89,6 +95,8 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
* Construct a new instance with the provided {@link JsonMapper.Builder}
* customized with the {@link tools.jackson.databind.JacksonModule}s found
* by {@link MapperBuilder#findModules(ClassLoader)}.
+ * @param builder the mapper builder to use
+ * @see #setTrustedPackages(String...)
* @see JsonMapper#builder()
*/
public JacksonJsonMessageConverter(JsonMapper.Builder builder) {
@@ -98,6 +106,8 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
/**
* Construct a new instance with the provided {@link JsonMapper}.
+ * @param mapper the mapper to use
+ * @see #setTrustedPackages(String...)
* @see JsonMapper#builder()
*/
public JacksonJsonMessageConverter(JsonMapper mapper) {
@@ -105,6 +115,15 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
this.mapper = mapper;
}
+ /**
+ * Specify the trusted Java packages for deserialization.
+ * @param trustedPackages the trusted Java packages for deserialization
+ * @since 7.0.8
+ */
+ public void setTrustedPackages(String... trustedPackages) {
+ this.trustedPackages = trustedPackages.clone();
+ }
+
/**
* Specify whether {@link #toMessage(Object, Session)} should marshal to a
* {@link BytesMessage} or a {@link TextMessage}.
@@ -168,6 +187,23 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
});
}
+ 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;
@@ -445,6 +481,10 @@ public class JacksonJsonMessageConverter implements SmartMessageConverter, BeanC
if (mappedClass != null) {
return this.mapper.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.mapper.constructType(typeClass);
diff --git a/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.java b/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.java
index b01f421512e..d67b68003bd 100644
--- a/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.java
+++ b/spring-jms/src/test/java/org/springframework/jms/support/converter/JacksonJsonMessageConverterTests.java
@@ -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;
@@ -131,6 +132,86 @@ class JacksonJsonMessageConverterTests {
assertThat(unmarshalled).as("Invalid result").isEqualTo(result);
}
+ @Test
+ void fromTextMessageUntrusted() throws Exception {
+ converter = new JacksonJsonMessageConverter();
+ 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 JacksonJsonMessageConverter();
+ 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 JacksonJsonMessageConverter();
+ 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 JacksonJsonMessageConverter();
+ 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 JacksonJsonMessageConverter();
+ 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();