zoukankan      html  css  js  c++  java
  • swagger序列化对example属性的特殊处理造成的json格式异常问题

    公司使用我定制过的swagger作为接口文档平台。昨日同事反映一个问题,说mvc控制器中新增加了一个接口,写法与其他接口无异,为什么加上他swagger接口文档平台就报错、注释掉他即正常?

    正好最近由于fastjson的反序列化绕过黑名单机制RCE漏洞事件,正研究fastjson及其他json序列化工具的反序列化安全问题,对这方面比较敏感。

    确认同事的描述无误,发现浏览器f12看到的错误原因是json解析异常。继而检查入参和返回的dto,发现在其中一个字段的example中填写了[2020/01/01, 2020/01/03]这样的值。

    中括号显然是json的保留字符,果然去掉中括号、或者在值前后加上双引号,都可以解决问题。

    但是事情并未结束,在我的认知中,swagger对example并未有特殊的说明,各种json序列化工具也不会擅自对String类型值进行判断输出推断后的结果,在引发操作错误的风险下费力的做类型推断和转换的脏活累活,swagger是出于什么考虑呢?

    首先回顾一下基本知识,swagger原理在这篇文章中有着很周到的叙述,简要提两点,就不再赘述了:

    1、利用spring plugin机制收集documention属性、扫描handlers apis、存入cache
    2、前端从cache获取数据进行展示

    详见https://blog.csdn.net/qq_25615395/java/article/details/70229139

    从swagger扫描机制入手,对其进行跟进,发现其直至存入document缓存(之后json序列化输出供前端调用),对应model的example值始终为String类型。

    那么剩余的流程只有json序列化,swagger使用jackson(ObjectMapper)作为json序列化工具。

    不难发现模型example属性的特殊json处理源于Swagger2JacksonModule(这里应该也允许我们自定义自己的Module定制序列化过程),

    Swagger2JacksonModule对swagger序列化文档模型输出时使用的ObjectMapper进行了注册。

    @Override
    public void setupModule(SetupContext context) {
    super.setupModule(context);
    ...

    context.setMixInAnnotations(Property.class, PropertyExampleSerializerMixin.class);
    }

    @JsonAutoDetect
    @JsonInclude(value = Include.NON_EMPTY)
    private interface PropertyExampleSerializerMixin {

    @JsonSerialize(using = PropertyExampleSerializer.class)
    Object getExample();

    class PropertyExampleSerializer extends StdSerializer<Object> {

    private final static Pattern JSON_NUMBER_PATTERN =
    Pattern.compile("-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?");

    @SuppressWarnings("unused")
    public PropertyExampleSerializer() {
    this(Object.class);
    }

    PropertyExampleSerializer(Class<Object> t) {
    super(t);
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException {
    if (canConvertToString(value)) {
    String stringValue = (value instanceof String) ? ((String) value).trim() : value.toString().trim();
    if (isStringLiteral(stringValue)) {
    String cleanedUp = stringValue.replaceAll("^"", "")
    .replaceAll(""$", "")
    .replaceAll("^'", "")
    .replaceAll("'$", "");
    gen.writeString(cleanedUp);
    } else if (isNotJsonString(stringValue)) {
    gen.writeRawValue(stringValue);
    } else {
    gen.writeString(stringValue);
    }
    } else {
    gen.writeObject(value);
    }
    }

    private boolean canConvertToString(Object value) {
    if (value instanceof java.lang.Boolean
    || value instanceof java.lang.Character
    || value instanceof java.lang.String
    || value instanceof java.lang.Byte
    || value instanceof java.lang.Short
    || value instanceof java.lang.Integer
    || value instanceof java.lang.Long
    || value instanceof java.lang.Float
    || value instanceof java.lang.Double
    || value instanceof java.lang.Void) {
    return true;
    }
    return false;
    }

    @VisibleForTesting
    boolean isStringLiteral(String value) {
    return (value.startsWith(""") && value.endsWith("""))
    || (value.startsWith("'") && value.endsWith("'"));
    }

    @VisibleForTesting
    boolean isNotJsonString(final String value) {
    // strictly speaking, should also test for equals("null") since {"example": null} would be valid JSON
    // but swagger2 does not support null values
    // and an example value of "null" probably does not make much sense anyway
    return value.startsWith("{") // object
    || value.startsWith("[") // array
    || "true".equals(value) // true
    || "false".equals(value) // false
    || JSON_NUMBER_PATTERN.matcher(value).matches(); // number
    }

    @Override
    public boolean isEmpty(SerializerProvider provider, Object value) {
    return internalIsEmpty(value);
    }

    @SuppressWarnings("deprecation")
    @Override
    public boolean isEmpty(Object value) {
    return internalIsEmpty(value);
    }

    private boolean internalIsEmpty(Object value) {
    return value == null || value.toString().trim().length() == 0;
    }
    }
    }

    可以看出若example属性不是基本数据类型的包装类或字符串,则按照对象序列化;继续判断是否以双引号/单引号开头结尾,如是按字符串序列化;继续判断是否以大括号/中括号开头,如是按对象/列表序列化,此处应是为了支持json字符串格式的example属性赋值,然而遇到手写并不规范的example值时就造成了输出的json格式无法解析的错误。

    至此,问题告一段落。

    /**
    * Updates the Example for the model
    *
    * @param example - example of the model
    * @return this
    * @deprecated @since 2.8.1 Use the one which takes in an Object instead
    */
    @Deprecated
    public ModelBuilder example(String example) {
    this.example = defaultIfAbsent(example, this.example);
    return this;
    }
    从源码的注释中可以看出自2.8.1版本以来,swagger将扫描api过程中接收example属性的类型字段由String改为Object,
    然而注解的成员变量必须是一个编译期常量,example属性如何接受一个object?swagger好像也并未告诉我们。

  • 相关阅读:
    机器视觉资料整理
    《用TCP/IP进行网络互连》读书笔记
    Win Form不能响应键盘事件
    C语言 字符串前加L的意义 如:L“A”
    UniCode 下 CString 转 char* 的方法(转)
    BATCH
    HALCON不支持的设备中,获取图像
    关于FragmentManager动态管理Fragment时Fragment生命周期的探究
    关于如何惟一地标识一台Android设备的综合性讨论
    如何使ActionBar不那么单调
  • 原文地址:https://www.cnblogs.com/feixuefubing/p/13181385.html
Copyright © 2011-2022 走看看