有如下两个接口:
@PostMapping public R<?> save(@RequestBody @Valid ArticleDTO dto) { return R.ok(service.saveArticle(dto)); } @PutMapping public R<?> updateById(@RequestBody @Valid ArticleDTO dto) { return R.ok(service.updateArticle(dto)); }
类似的请求参数(同一个页面的表单), 保存时 dto 中大部分属性都是 NULL
更新时则没有任何问题,
确认请求参数已发出, Spring MVC org.springframework.web.servlet.DispatcherServlet
中断点还能看到, 到达Controller
中参数丢失
请求参数包含一个特殊属性: coverUrl
与之对应的控制器入参对象中 coverUrl
类型为 String
(AVue Upload 类型表单组件默认值)
{ ... "coverUrl": [], "images": [], ... }
项目中 ....common.xss.core.JacksonXssClean
重写了 com.fasterxml.jackson.databind.JsonDeserializer#deserialize
方法, 处理所有的反序列化
deserialize
方法实现如下:
@Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // XSS filter String text = p.getValueAsString(); if (text == null) { return null; } else if (XssHolder.isEnabled()) { String value = XssUtil.clean(text); log.trace("Json property value:{} cleaned up by mica-xss, current value is:{}.", text, value); return value; } else { return text; } }
JsonParser.getValueAsString()
方法注释:
方法将尝试将当前标记的值转换为{@link java.lang.String}。JSON字符串自然映射;标量值被转换为它们的文本表示形式。如果表示不能转换为字符串值(包括结构化类型,如对象、数组和空令牌),将返回默认值null;没有抛出异常。
当处理到请求参数: coverUrl
的属性 [
时, getValueAsString()
直接返回 null
导致方法退出. 后续没有抛出异常, 而是中断所有解析! 也就导致了 content
属性没能正确赋值
而更新方法携带的 coverUrl 属性是查询是返回的, 默认为 NULL, 会跳过 deserialize()
方法的解析. 如果查询结果 coverUrl
非 NULL, 再次提交同样会丢失 coverUrl
之后解析的所有属性 (解析顺序未知, 和入参类型属性的定义顺序, 请求参数的传递顺序 无关)
2021-09-11 02:15:23.502 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : JSON Token 当前属性名 当前值 当前文本 ValueAsString(NULL) VALUE_STRING type ArticleDTO(images=null) 2 2 2021-09-11 02:15:23.624 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 触发XSS过滤, 原: 2 过滤后: 2 2021-09-11 02:15:23.625 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : JSON Token 当前属性名 当前值 当前文本 ValueAsString(NULL) VALUE_STRING title ArticleDTO(images=null) 文章标题 文章标题 2021-09-11 02:15:23.625 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 触发XSS过滤, 原: 文章标题 过滤后: 文章标题 2021-09-11 02:15:23.625 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : JSON Token 当前属性名 当前值 当前文本 ValueAsString(NULL) START_ARRAY coverUrl NULL [ [ 2021-09-11 02:15:23.625 ERROR 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 下一个令牌: END_ARRAY 2021-09-11 02:15:23.625 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 触发XSS过滤, 原: [ 过滤后: [ 2021-09-11 02:15:23.626 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : JSON Token 当前属性名 当前值 当前文本 ValueAsString(NULL) VALUE_STRING profile ArticleDTO(images=[]) 2021-09-11 02:15:23.626 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 触发XSS过滤, 原: 过滤后: 2021-09-11 02:15:23.626 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : JSON Token 当前属性名 当前值 当前文本 ValueAsString(NULL) VALUE_STRING content ArticleDTO(images=[]) <p>详情内容</p> <p>详情内容</p> 2021-09-11 02:15:23.629 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 触发XSS过滤, 原: <p>详情内容</p> 过滤后: <p>详情内容</p> 2021-09-11 02:15:23.631 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : JSON Token 当前属性名 当前值 当前文本 ValueAsString(NULL) VALUE_STRING visibleStatus ArticleDTO(images=[]) 0 0 2021-09-11 02:15:23.632 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 触发XSS过滤, 原: 0 过滤后: 0 2021-09-11 02:15:23.633 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : JSON Token 当前属性名 当前值 当前文本 ValueAsString(NULL) VALUE_STRING validStatus ArticleDTO(images=[]) 0 0 2021-09-11 02:15:23.633 WARN 10560 --- [ XNIO-1 task-1] n.v.gms.common.xss.core.JacksonXssClean : 触发XSS过滤, 原: 0 过滤后: 0 2021-09-11 02:15:23.775 INFO 10560 --- [ XNIO-1 task-1] n.v.g.cms.controller.ArticleController : 新增文章: {"visitorTotal":0,"images":[],"profile":"","sort":9999,"title":"文章标题","type":"2","content":"<p>详情内容</p>","coverUrl":"[","visibleStatus":"0","validStatus":"0","channelId":0}
删除 coverUrl
属性即可
一个有限的解决方案, 只能处理空数组, 修改 deserialize
方法实现:
@Override public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { // XSS filter // String text = p.getValueAsString(); // Method can be called for any token type. String text = p.getText(); // 当前 token 为数组开始字符 if (p.isExpectedStartArrayToken()) { // 快进到下一个 token JsonToken jsonToken = p.nextToken(); } if (text == null) { return null; } else if (XssHolder.isEnabled()) { String value = XssUtil.clean(text); log.trace("Json property value:{} cleaned up by mica-xss, current value is:{}.", text, value); return value; } else { return text; } }
对于非空数组, if 中的 jsonToken
会得到 com.fasterxml.jackson.core.JsonToken#VALUE_STRING
类型枚举, 然后在退出方法后报错