mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
Add trusted packages to JacksonJsonMessageConverter
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. See gh-36791
This commit is contained in:
committed by
Brian Clozel
parent
322eef2942
commit
fe68e337b2
+40
@@ -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.
|
||||
*
|
||||
* <p>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<Class<?>, 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);
|
||||
|
||||
+81
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user