线上环境小程序商城突然出现一部分用户拉不起支付的情况,查询日志发现有大量"签名失败"。
微信统一支付请求返回:
<xml><return_code><![CDATA[FAIL]]></return_code>
<return_msg><![CDATA[签名错误,请检查后再试]]></return_msg>
</xml>
微信签名是根据支付字符串md5生成的,所以这种偶发性的错误,应该不是md5签名方法的问题。
private String encodeMd5(String inStr) {
StringBuffer hexValue = new StringBuffer();
MessageDigest md5 = null;
try {
md5 = MessageDigest.getInstance("MD5");
byte[] byteArray = inStr.getBytes("UTF-8");
byte[] md5Bytes = md5.digest(byteArray);
for (int i = 0; i < md5Bytes.length; i++) {
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16) {
hexValue.append("0");
}
hexValue.append(Integer.toHexString(val));
}
} catch (Exception e) {
System.out.println(e.toString());
e.printStackTrace();
return "";
}
return hexValue.toString();
}
md5 = MessageDigest.getInstance("MD5") 虽然是创建了一个新的实例,但是仍然有线程安全问题,官方给的是通过md5.digest避免或者换apache有线程安全的md5工具类。本项目中的md5加密是没有线程安全问题的,单独测试过,没有问题。
接着查代码,在生成sign后,最后需要将sign和其他要素组成为一个xml,在这块,用了一个对象转xml方法,核心代码如下:
public String toXML(Object request) {
XStream xstreamReq = new XStream(new XppDriver());
xstreamReq.alias("xml", request.getClass());
String requestXML = xstreamReq.toXML(request).replace("\n", "").replace("__", "_");
return requestXML;
}
在这个方法中,生成的xml的字符串做了两次替换,一次是将换行符去掉,第二次是将两个下划线替换成一个下换线。第一个替换可以理解,第二个替换不知道什么作用,所以去掉后测试下。发现,这样写的目的是简单粗暴的解决对象属性中存在"_
"时,转换后的xml文件的标签变成了"__
"两个下划线。例如有一个对象的其中一个属性为xx_xxx,xstreamReq.toXML(request) 生成的xml中这个属性的标签成了<xx__xxx></xx__xxx>,而通过.replace("__", "_")
可以简单粗暴的将这个问题解决掉。
但是这样写只解决了标签的问题,同时引入了新的bug。微信openID是存在包含“__
”两个下划线的情况的,这种情况下,微信的openID被替换成了错误的,而签名是正确的,最终导致签名和xml数据不一致,签名失败。其效果相当于将微信用户openId包含两个下划线的用户拉入了支付黑名单,永远拉不起支付。在网上搜索了下,很多文章都在用上面方法生成最终请求xml做拉起支付请求。
问题找到就好办了,解决xml生成时,因对象属性存在"_
",防止生成的xml标签将这类对象属性标签转成“__
”两个下划线,解决方案如下:
public String toXMLNotreplace(Object request) {
XStream xstreamReq = new XStream(new Xpp3Driver(new NoNameCoder()));
xstreamReq.alias("xml", request.getClass());
String requestXML = xstreamReq.toXML(request).replace("\n", "");
return requestXML;
}
网友评论