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好像也並未告訴我們。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM