大家好,我是烤鸭:
今天分享一个问题,使用fastjson 导致签名不过。
1. 问题复现:
fastjson 1.2.4
获取返回值:
{"data":[{"id":"120190422110857284042111114","bankAccountType":"DEBIT","isMainCard":"0","bindId":"120190422110857284042111114","bankCardPhone":"15010311111","realName":null,"partBankAccountNo":"1199","bindChannel":"11111_PROTOCOL","masterAccountNo":null,"branchBankName":"建设银行","bankCode":"CCB"}],"sign":"nIBnR5YpoiYg4CwYIs5l1K2ne30X7A55YtjJH9teHYcZkRcPcI/ECg/TzKiMCFtKssOk1F8hXjC3Of2NhgYcduZAw4VjBb4HMuz9qRgSlZht0CXsMh8RVyysbJcKe8zhd4mansWzlxnIK2Kh3YnKf9Hzd/cHlcczr6UnDeifbZk="}
获取返回值中的数组:
[{"bankCode":"CCB","bankAccountType":"DEBIT","branchBankName":"建设银行","partBankAccountNo":"1199","id":"120190422110857284042111114","bindChannel":"11111_PROTOCOL","isMainCard":"0","bindId":"120190422110857284042111114","bankCardPhone":"15010311111"}]
fastjson 1.1.32
获取返回值:
{"data":[{"id":"120190422110857284042111114","bankAccountType":"DEBIT","isMainCard":"0","bindId":"120190422110857284042111114","bankCardPhone":"15010311111","realName":null,"partBankAccountNo":"1199","bindChannel":"11111_PROTOCOL","masterAccountNo":null,"branchBankName":"建设银行","bankCode":"CCB"}],"sign":"nIBnR5YpoiYg4CwYIs5l1K2ne30X7A55YtjJH9teHYcZkRcPcI/ECg/TzKiMCFtKssOk1F8hXjC3Of2NhgYcduZAw4VjBb4HMuz9qRgSlZht0CXsMh8RVyysbJcKe8zhd4mansWzlxnIK2Kh3YnKf9Hzd/cHlcczr6UnDeifbZk="}
获取返回值中的数组:
[{"bankAccountType":"DEBIT","bankCardPhone":"15010311111","bankCode":"CCB","bindChannel":"11111_PROTOCOL","bindId":"120190422110857284042111114","branchBankName":"建设银行","id":"120190422110857284042111114","isMainCard":"0","partBankAccountNo":"1199"}]
由于引入第三方的jar包,在返回值验签的时候报错。第三方jar中使用的fastjson 版本是 1.1.32,而我们项目中使用的是 1.2.4。
可以看到取出数组数据之后的不同点。一个是按字母升序排列的,一个是按自然排序(放入的顺序)。真的是坑....
2. 源码分析:
fastjson 1.2.4
com.alibaba.fastjson.serializer.ListSerializer
代码更简洁了,去掉了对array的排序功能
public final void write(JSONSerializer serializer, Object object, Object fieldName, Type fieldType, int features)
throws IOException {
boolean writeClassName = serializer.isEnabled(SerializerFeature.WriteClassName);
SerializeWriter out = serializer.getWriter();
Type elementType = null;
if (writeClassName) {
if (fieldType instanceof ParameterizedType) {
ParameterizedType param = (ParameterizedType) fieldType;
elementType = param.getActualTypeArguments()[0];
}
}
if (object == null) {
if (out.isEnabled(SerializerFeature.WriteNullListAsEmpty)) {
out.write("[]");
} else {
out.writeNull();
}
return;
}
List<?> list = (List<?>) object;
if (list.size() == 0) {
out.append("[]");
return;
}
SerialContext context = serializer.getContext();
serializer.setContext(context, object, fieldName, 0);
ObjectSerializer itemSerializer = null;
try {
if (out.isEnabled(SerializerFeature.PrettyFormat)) {
out.append('[');
serializer.incrementIndent();
int i = 0;
for (Object item : list) {
if (i != 0) {
out.append(',');
}
serializer.println();
if (item != null) {
if (serializer.containsReference(item)) {
serializer.writeReference(item);
} else {
itemSerializer = serializer.getObjectWriter(item.getClass());
SerialContext itemContext = new SerialContext(context, object, fieldName, 0, 0);
serializer.setContext(itemContext);
itemSerializer.write(serializer, item, i, elementType, 0);
}
} else {
serializer.getWriter().writeNull();
}
i++;
}
serializer.decrementIdent();
serializer.println();
out.append(']');
return;
}
out.append('[');
int i = 0;
for (Object item : list) {
if (i != 0) {
out.append(',');
}
if (item == null) {
out.append("null");
} else {
Class<?> clazz = item.getClass();
if (clazz == Integer.class) {
out.writeInt(((Integer) item).intValue());
} else if (clazz == Long.class) {
long val = ((Long) item).longValue();
if (writeClassName) {
out.writeLongAndChar(val, 'L');
} else {
out.writeLong(val);
}
} else {
SerialContext itemContext = new SerialContext(context, object, fieldName, 0, 0);
serializer.setContext(itemContext);
if (serializer.containsReference(item)) {
serializer.writeReference(item);
} else {
itemSerializer = serializer.getObjectWriter(item.getClass());
itemSerializer.write(serializer, item, i, elementType, 0);
}
}
}
i++;
}
out.append(']');
} finally {
serializer.setContext(context);
}
}
fastjson 1.1.32
StringDeserializer
public static <T> T deserialze(DefaultJSONParser parser) {
final JSONLexer lexer = parser.getLexer();
if (lexer.token() == JSONToken.LITERAL_STRING) {
String val = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
return (T) val;
}
if (lexer.token() == JSONToken.LITERAL_INT) {
String val = lexer.numberString();
lexer.nextToken(JSONToken.COMMA);
return (T) val;
}
Object value = parser.parse();
if (value == null) {
return null;
}
return (T) value.toString();
}
public Object parse(Object fieldName) {
final JSONLexer lexer = getLexer();
switch (lexer.token()) {
case SET:
lexer.nextToken();
HashSet<Object> set = new HashSet<Object>();
parseArray(set, fieldName);
return set;
case TREE_SET:
lexer.nextToken();
TreeSet<Object> treeSet = new TreeSet<Object>();
parseArray(treeSet, fieldName);
return treeSet;
case LBRACKET:
JSONArray array = new JSONArray();
parseArray(array, fieldName);
return array;
case LBRACE:
JSONObject object = new JSONObject();
return parseObject(object, fieldName);
case LITERAL_INT:
Number intValue = lexer.integerValue();
lexer.nextToken();
return intValue;
case LITERAL_FLOAT:
Object value = lexer.decimalValue(isEnabled(Feature.UseBigDecimal));
lexer.nextToken();
return value;
case LITERAL_STRING:
String stringLiteral = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
try {
if (iso8601Lexer.scanISO8601DateIfMatch()) {
return iso8601Lexer.getCalendar().getTime();
}
} finally {
iso8601Lexer.close();
}
}
return stringLiteral;
case NULL:
lexer.nextToken();
return null;
case TRUE:
lexer.nextToken();
return Boolean.TRUE;
case FALSE:
lexer.nextToken();
return Boolean.FALSE;
case NEW:
lexer.nextToken(JSONToken.IDENTIFIER);
if (lexer.token() != JSONToken.IDENTIFIER) {
throw new JSONException("syntax error");
}
lexer.nextToken(JSONToken.LPAREN);
accept(JSONToken.LPAREN);
long time = ((Number) lexer.integerValue()).longValue();
accept(JSONToken.LITERAL_INT);
accept(JSONToken.RPAREN);
return new Date(time);
case EOF:
if (lexer.isBlankInput()) {
return null;
}
throw new JSONException("unterminated json string, pos " + lexer.getBufferPosition());
case ERROR:
default:
throw new JSONException("syntax error, pos " + lexer.getBufferPosition());
}
}
重点在于:
case LBRACKET:
JSONArray array = new JSONArray();
parseArray(array, fieldName);
return array;
DefaultJSONParser:
public final void parseArray(final Collection array, Object fieldName) {
final JSONLexer lexer = getLexer();
if (lexer.token() == JSONToken.SET || lexer.token() == JSONToken.TREE_SET) {
lexer.nextToken();
}
if (lexer.token() != JSONToken.LBRACKET) {
throw new JSONException("syntax error, expect [, actual " + JSONToken.name(lexer.token()) + ", pos "
+ lexer.pos());
}
lexer.nextToken(JSONToken.LITERAL_STRING);
ParseContext context = this.getContext();
this.setContext(array, fieldName);
try {
for (int i = 0;; ++i) {
if (isEnabled(Feature.AllowArbitraryCommas)) {
while (lexer.token() == JSONToken.COMMA) {
lexer.nextToken();
continue;
}
}
Object value;
switch (lexer.token()) {
case LITERAL_INT:
value = lexer.integerValue();
lexer.nextToken(JSONToken.COMMA);
break;
case LITERAL_FLOAT:
if (lexer.isEnabled(Feature.UseBigDecimal)) {
value = lexer.decimalValue(true);
} else {
value = lexer.decimalValue(false);
}
lexer.nextToken(JSONToken.COMMA);
break;
case LITERAL_STRING:
String stringLiteral = lexer.stringVal();
lexer.nextToken(JSONToken.COMMA);
if (lexer.isEnabled(Feature.AllowISO8601DateFormat)) {
JSONScanner iso8601Lexer = new JSONScanner(stringLiteral);
if (iso8601Lexer.scanISO8601DateIfMatch()) {
value = iso8601Lexer.getCalendar().getTime();
} else {
value = stringLiteral;
}
iso8601Lexer.close();
} else {
value = stringLiteral;
}
break;
case TRUE:
value = Boolean.TRUE;
lexer.nextToken(JSONToken.COMMA);
break;
case FALSE:
value = Boolean.FALSE;
lexer.nextToken(JSONToken.COMMA);
break;
case LBRACE:
JSONObject object = new JSONObject();
value = parseObject(object, i);
break;
case LBRACKET:
Collection items = new JSONArray();
parseArray(items, i);
value = items;
break;
case NULL:
value = null;
lexer.nextToken(JSONToken.LITERAL_STRING);
break;
case RBRACKET:
lexer.nextToken(JSONToken.COMMA);
return;
default:
value = parse();
break;
}
array.add(value);
checkListResolve(array);
if (lexer.token() == JSONToken.COMMA) {
lexer.nextToken(JSONToken.LITERAL_STRING);
continue;
}
}
} finally {
this.setContext(context);
}
}
com.alibaba.fastjson.serializer.SerializeWriter
writeStringWithDoubleQuote() 重排序
System.arraycopy(buf, lastSpecialIndex + 1, buf, lastSpecialIndex + 2, end - lastSpecialIndex - 1);
buf[lastSpecialIndex] = '\\';
buf[++lastSpecialIndex] = replaceChars[(int) lastSpecial];
小结:
由于 fastjson 版本 1.1.32 在反序列化的时候,自动排序了。而 1.2.4 以后的版本没有自动排序,如果加解密的json 版本不一样,就会导致加解密出错。
去阿里的仓库找fastjson,发现已经没有 1.2.4之前的版本了,说明之前的版本确实有问题。