之前寫過一個解密json格式加密的,我以為xml的和json的差不多,是上上個星期五吧,我的同事也是在做微信公眾號裡面的消息推送解密,發現好像只能使用xml加密格式的發送到伺服器,我們去年也做過企業微信的那個消息推送的解密,真的是,感覺雖然都差不多,但是三者如果使用同樣的代碼的話完全不能復用,只是你 ...
之前寫過一個解密json格式加密的,我以為xml的和json的差不多,是上上個星期五吧,我的同事也是在做微信公眾號裡面的消息推送解密,發現好像只能使用xml加密格式的發送到伺服器,我們去年也做過企業微信的那個消息推送的解密,真的是,感覺雖然都差不多,但是三者如果使用同樣的代碼的話完全不能復用,只是你做過一個之後,如果在做其他的就會瞭解他的原理。我在github上面正在完善node對於微信的各種API的配置和使用,地址 https://github.com/zzz111111/wx_app 裡面有所有的源代碼,使用了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 }
放到伺服器上面的列印是這樣的。
那些看不到的信息,可以在加密的函數中列印看到到底返回來什麼。
回覆的消息圖。
這一篇文章的解析過程沒有之前那個詳細,如果想瞭解更多,可以兩篇互相補充的看一看。 https://www.cnblogs.com/z937741304/p/10380496.html
如果你看了我的文章學習到了,並且解決了你的問題我會非常高興,如有不足之處,希望各位可以指正,謝謝!