环境: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 → XssStringFormatterGenericConverter 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;//如果参数为空字符串,则直接返回nullif (!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> {@Overridepublic void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {if (value != null) {String encodedValue = StringEscapeUtils.escapeHtml4(value);gen.writeString(encodedValue);}}}
2 注入ObjectMapper
@Bean@Primarypublic 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> {@Overridepublic void serialize(String value, JsonGenerator gen, SerializerProvider serializers) throws IOException {if (value != null) {gen.writeString(value);}}}
参考文档:
2022-09