Spring MVC 解析JSON入参意外的丢失参数

表现

有如下两个接口:

@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 类型枚举, 然后在退出方法后报错

Spring MVC 解析JSON入参意外的丢失参数

上一篇:奇怪报错信息“db already exists with different case already have”解决方法


下一篇:Netty编解码器&TCP粘包拆包