环境
:springboot 2.7.3
关键词
: HandlerMethodArgumentResolverComposite
、FormattingConversionService
、GenericConversionService
最近小伙伴们发现了一个问题, 就是前端参数传的空字符串,到后端变成了null,一时摸不到头脑,让我帮一起看看问题所在。
是否通过@InitBinder
注册了registerCustomEditor
对String类型的参数做了转换
是否是Filter
过滤器中对参数做了再次处理
json
后加密,后端通过HttpServletRequestWrapper
解密重写参数,然后也不是是否自定义了Converter
(implements org.springframework.core.convert.converter.Converter.Converter
<String, String>) 对空字符串做了特殊转换,
是否自定义了Formatter
(implements org.springframework.format.Formatter.Formatter
<String>)对字符串进行了转换
这个真的有:
但是代码如上,只是在输出的时候进行了转义,防止XSS攻击,而在parse方法中并没有做任何操作
我猜测是在参数转换那里出了问题,因此把断点打在HandlerMethodArgumentResolverComposite
的resolveArgument
方法上。
HandlerMethodArgumentResolverComposite
#resolveArgument
→ RequestParamMethodArgumentResolver
(AbstractNamedValueMethodArgumentResolver
)#resolveArgument
→
DataBinder
#convertIfNecessary
→
TypeConverterSupport
#convertIfNecessary
→ TypeConverterDelegate
#convertIfNecessary
→ GenericConversionService
#convert
public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {
Assert.notNull(targetType, "Target type to convert to cannot be null");
if (sourceType == null) {
Assert.isTrue(source == null, "Source must be [null] if source type == [null]");
return handleResult(null, targetType, convertNullSource(null, targetType));
}
if (source != null && !sourceType.getObjectType().isInstance(source)) {
throw new IllegalArgumentException("Source to convert from must be an instance of [" +
sourceType + "]; instead it was a [" + source.getClass().getName() + "]");
}
//此处获取到的是FormattingConversionService,且conversionService属性中的
//converterCache包含的为String → XssStringFormatter
GenericConverter converter = getConverter(sourceType, targetType);
if (converter != null) {
Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
return handleResult(sourceType, targetType, result);
}
return handleConverterNotFound(source, sourceType, targetType);
}
通过ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
进入到的方法为:
FormattingConversionService$ParserConverter#convert
,在这个方法中如果参数为空字符串,则直接返回null
public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
String text = (String) source;
//如果参数为空字符串,则直接返回null
if (!StringUtils.hasText(text)) {
return null;
}
Object result;
try {
result = this.parser.parse(text, LocaleContextHolder.getLocale());
}
catch (IllegalArgumentException ex) {
throw ex;
}
catch (Throwable ex) {
throw new IllegalArgumentException("Parse attempt failed for value [" + text + "]", ex);
}
TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass());
if (!resultType.isAssignableTo(targetType)) {
result = this.conversionService.convert(result, resultType, targetType);
}
return result;
}
但是当不在项目中注册自定义的Formatter的时候,ConversionUtils.invokeConverter(converter, source, sourceType, targetType);
进入的方法却是GenericConversionService
的convert
方法,最重要的区别就是在这里了。
至于为什么会使用不同的convert,就在 GenericConversionService
#getConverter(TypeDescriptor sourceType, TypeDescriptor targetType)
中,本文就不展开赘述, 可以参考问候的参考文档第一篇。
ObjectMapper
注册一个Xss
解析器 项目中的XssStringFormatter
本意是字符串数据输入到前端的时候,通过StringEscapeUtils.escapeHtml4(object)
进行html转义,一定程度上预防XSS攻击 。
项目使用的为springboot
+ thymeleaf
, 向页面传参主要通过两种方式一个是thymeleaf
模板中直接使用变量,而th:text
是默认转义的。另外就是ajax返回的json
数据了,在注入的ObjectMappeing
中注册一个Xss
解析器即可:
1 定义 XssStringJsonSerializer
/**
* 描述: 写入前端的json字段做xss处理
* @author Vic.xu
*/
public class XssStringJsonSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value != null) {
String encodedValue = StringEscapeUtils.escapeHtml4(value);
gen.writeString(encodedValue);
}
}
}
2 注入ObjectMapper
@Bean
@Primary
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder build) {
logger.info("register xssObjectMapper");
ObjectMapper objectMapper = build.createXmlMapper(false).build();
SimpleModule simpleModule = new SimpleModule(XssStringJsonSerializer.class.getSimpleName());
simpleModule.addSerializer(String.class, new XssStringJsonSerializer());
objectMapper.registerModule(simpleModule);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return objectMapper;
}
3 对于不需要进行转义的字段,则在字段上加IgnoreXssStringJsonSerializer
注解
/**
* 写入前端的字段忽略xss处理 {@link XssStringJsonSerializer}
* @author Vic.xu
*/
public class IgnoreXssStringJsonSerializer extends JsonSerializer<String> {
@Override
public void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
if (value != null) {
gen.writeString(value);
}
}
}
参考文档:
2022-09