大家好,我是烤鸭:
今天分享一个问题,使用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之前的版本了,说明之前的版本确实有问题。