node.js解析微信消息推送xml格式加密的消息
之前写过一个解密json格式加密的,我以为xml的和json的差不多,是上上个星期五吧,我的同事也是在做微信公众号里面的消息推送解密,发现好像只能使用xml加密格式的发送到服务器,我们去年也做过企业微信的那个消息推送的解密,真的是,感觉虽然都差不多,但是三者如果使用同样的代码的话完全不能复用,只是你做过一个之后,如果在做其他的就会了解他的原理。我在github上面正在完善node对于微信的各种api的配置和使用,地址 里面有所有的源代码,使用了express框架。
xml格式的解密实际上和json格式的是差不多的,只不过是express框架的一些问题影响到了我们,使用了body-parser中间件之后json格式的数据会加到 req.body 里面去,但是xml格式的话我们一般没有使用其他的中间件去把xml格式的数据加入到 req.body 上面。这里我用到了 express-xml-bodyparser 中间件把xml解析出来了,也可以不使用这些,直接使用原生的代码也可以拿到这些数据。
使用中间件的代码。
1 const xmlparser = require('express-xml-bodyparser'); 2 3 app.use(xmlparser()); /* 为了解析微信的 xml 格式的文件而加入的 */ 4 5 6 router.post("/你的地址", (req, res) => { 7 console.log('接收到了请求url中'); 8 console.log(req.query); 9 console.log('接收到了请求,请求体中'); 10 console.log(req.body); 11 });
不使用中间件也是可以获取到的,写这个的原因是因为同事用的是koa2框架,并没有找到解析xml格式的中间件,所以后来在网上找了一些资料,自己写了一个。
刚才在测验的时候,我在原生的req对象上面找到了请求的内容在哪里。。。,我也真是个天才。 koa2框架。
1 router.post('/xml', async ctx => { 2 console.log('请求到了xml接口'); 3 4 console.log(ctx.req._readablestate.buffer); 5 console.log('其它的内容 --- head'); 6 var buf1 = ctx.req._readablestate.buffer.head.data; 7 console.log(buf1); 8 console.log(new buffer(buf1).tostring()); 9 console.log('其它的内容 --- tail'); 10 var buf2 = ctx.req._readablestate.buffer.tail.data; 11 console.log(buf2); 12 console.log(new buffer(buf2).tostring()); 13 ctx.body = 'xml'; 14 })
那两个被buffer对象转换之后的都是xml格式的对象。
这里的xml格式的内容还是一个字符串,我们先用正常的方式来获取请求体中内容,再来解析它。
上面的纯属瞎搞,一般我们的属性的名字前面加了一个 _ (也就是下划线)就代表这个东西不希望外部访问到吧,所以用一个正常人的操作。正常的来获取它。
1 router.post('/xmltest', async ctx => { 2 console.log('请求到了xml接口'); 3 var data = ''; 4 ctx.req.on('data', (chunk) => { 5 data += chunk; 6 }); 7 ctx.req.on('end', () => { 8 console.log('传输数据结束'); 9 console.log(data); 10 }); 11 ctx.body = 'xml'; 12 });
下面就是请求他的东西。
这个样子的才算是正常的,拿到请求的xml格式的东西,接下来我们使用 xml2js 模块来解析它,把这个东西转换为json格式的对象。
npm i -s xml2js
xml转json 和json 转xml的方法
1 const xml = { 2 xmltojson(str){ 3 return new promise((resolve, reject) => { 4 const parsestring = xml2js.parsestring 5 parsestring(str, (err, result) => { 6 if (err) { 7 reject(err) 8 } else { 9 resolve(result) 10 } 11 }) 12 }) 13 }, 14 jsontoxml() { 15 const builder = new xml2js.builder() 16 return builder.buildobject(obj) 17 } 18 19 }
我们可以使用koa2这种洋葱模型的方式,在前一个方法中解析xml文件,把他放到 ctx.req.body的上面访问到它。
1 router.post('/xml2', async (ctx , next)=> { 2 if (ctx.method == 'post' && ctx.is('text/xml')) { 3 let promise = new promise(function (resolve, reject) { 4 let buf = '' 5 ctx.req.setencoding('utf8') 6 ctx.req.on('data', (chunk) => { 7 console.log('接收数据'); 8 9 console.log(chunk); 10 buf += chunk 11 12 }) 13 ctx.req.on('end', () => { 14 xml.xmltojson(buf) 15 .then(resolve) 16 .catch(reject) 17 }) 18 }) 19 20 await promise.then((result) => { 21 ctx.req.body = result 22 }) 23 .catch((e) => { 24 e.status = 400 25 }) 26 27 next() 28 } else { 29 await next() 30 } 31 }, async ctx => { 32 console.log(ctx.req.body); 33 34 console.log(ctx.req.body.xml.tousername[0]) 35 ctx.body = '第二次返回信息'; 36 });
上面就可以解析到了,把xml格式转为json对象了。
这篇文章的主题就是解析微信推送xml消息,咱们不能跑题,上面这些都是铺垫,都是基础,我们当时就是因为拿不到xml的数据,找了好久的方法,express直接安装上面所说的中间件就好了,koa2由于没有找到适合的中间件,可以自己写一下也可以拿到。
闲话不多说,拿到之后其实就和json格式的没有区别了,可以去看看json格式的解析。
由于我是使用的express框架,把方法单离出来了,所以把代码发出来,看详细的可以去我的 github上面去看,上面也给了地址了。
1 /** 2 * 此处方法解析的是微信消息加密 xml 格式的 3 * 4 * 过程介绍为 5 * 1. 先拿到消息 url 中的字符串,并且拿到消息体中的密文体 6 * 2. 对 url 和 密文体 进行微信方面提供的加密方法验证是否等于消息体签名,验证消息是否为微信转发过来的 7 * 3. 第 2 步验证成功之后,对微信消息进行解密,解密函数在工具函数中 8 * 9 * url地址中的内容 10 * 11 * @params {string} signature 签名串 12 * @params {string} timestamp 时间戳 13 * @params {string} nonce 随机串 14 * @params {string} encrypt_type 加密类型(aes) 15 * @params {string} openid 16 * @params {string} msg_signature 消息体签名.用于验证消息体的正确性 17 * 18 * 请求体中的内容 -- 解析后 19 * @params {string} tousername 小程序的原始id 20 * @params {string} encrypt 加密后的消息字符串 21 * 22 */ 23 exports.handlecustomerserverxml = (req, res) => { 24 console.log('接收到了请求url中'); 25 console.log(req.query); 26 console.log('接收到了请求,请求体中'); 27 console.log(req.body); 28 const {signature,timestamp, nonce, encrypt_type, openid, msg_signature} = req.query; 29 const msg_encrypt = req.body.xml.encrypt[0]; 30 31 // 验证消息的正确性 32 const dev_msg_signature = sha1(config.pushtoken, timestamp, nonce, msg_encrypt); 33 if(dev_msg_signature == msg_signature){ 34 // 签名消息正确,来自微信服务器 解密 35 const lastdata = utils.decryptxml({ 36 aeskey: config.server.encodingaeskey, 37 text: msg_encrypt, 38 corpid: config.app.appid 39 }); 40 console.log('msg函数中接收到的数据内容'); 41 42 console.log(lastdata); 43 console.log('收到的消息为 --------- ' + lastdata.msg.xml.content[0]); 44 45 var msgarr = { 46 '新年好': '你tm新年也好啊', 47 '值班': '老子今天不上班,你值你m呢', 48 '你好': '你好', 49 '什么': '你在说什么呢?' 50 }; 51 var replymsg = msgarr[lastdata.msg.xml.content[0]]; 52 if(replymsg){ 53 zy.msg.textmsg(openid, openid, replymsg) 54 .then(res => { 55 console.log('消息发送成功!'); 56 console.log(res); 57 }) 58 .catch(err => { 59 console.log('消息发送失败'); 60 console.log(err); 61 }) 62 }else{ 63 zy.msg.textmsg(openid, openid, '你瞧瞧你说的是人话吗?') 64 .then(res => { 65 console.log('消息发送成功!'); 66 console.log(res); 67 }) 68 .catch(err => { 69 console.log('消息发送失败'); 70 console.log(err); 71 }) 72 } 73 res.send('success'); 74 }else{ 75 console.log('非微信服务器试图发送消息给我!!'); 76 res.send('你在玩啥呢??'); 77 } 78 }
sha1方法
1 /* 2 @explain sh1加密 3 @version 1.0.1 4 5 @author : z 6 @data : 2019-2-13 7 8 @params {string, string...} a,b,c…… 9 @return {string} 加密完成后的字符串 10 */ 11 exports.sha1 = function (...arr) { 12 return crypto.createhash('sha1').update(arr.sort().join('')).digest('hex'); 13 };
只针对于xml格式的解析方法。
1 /** 2 * 解析微信上传消息 此方法只解析 xml 格式 3 * @versin 1.0.0 4 * @data 2019-3-21 5 * 6 * @params {object} 7 * obj.aeskey:解密的aeskey值 8 * obj.text: 需要解密的密文 9 * obj.corpid: 企业的id / 微信小程序的appid 10 * 11 * @return {object} 12 * obj.noncestr 随机数 13 * obj.msg_len 微信密文的len 14 * obj.msg 解密后的明文 json 格式 15 * obj.corpid 错乱的 appid 尾部填充了些东西,可以舍弃,因为我们知道自己的appid是多少,不需要这里告诉我们 16 * 17 */ 18 exports.decryptxml = function(obj){ 19 let aeskey = buffer.from(obj.aeskey + '=', 'base64'); 20 const cipherencoding = 'base64'; 21 const clearencoding = 'utf8'; 22 const cipher = crypto.createdecipheriv('aes-256-cbc',aeskey,aeskey.slice(0, 16)); 23 cipher.setautopadding(false); // 是否取消自动填充 不取消 24 let this_text = cipher.update(obj.text, cipherencoding, clearencoding) + cipher.final(clearencoding); 25 let xmltext = ''; 26 xml2js.parsestring(this_text.substring(20,this_text.lastindexof(">")+1), function(err, result){ 27 if(err) throw err; 28 xmltext = result; 29 }); 30 return { 31 noncestr:this_text.substring(0,16), 32 msg_len:this_text.substring(16,20), 33 msg:xmltext, 34 corpid: this_text.substring(this_text.lastindexof(">")+1) 35 } 36 }
放到服务器上面的打印是这样的。
那些看不到的信息,可以在加密的函数中打印看到到底返回来什么。
回复的消息图。
这一篇文章的解析过程没有之前那个详细,如果想了解更多,可以两篇互相补充的看一看。
如果你看了我的文章学习到了,并且解决了你的问题我会非常高兴,如有不足之处,希望各位可以指正,谢谢!