179 lines
9.9 KiB
Markdown
179 lines
9.9 KiB
Markdown
> 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [developer.work.weixin.qq.com](https://developer.work.weixin.qq.com/tutorial/detail/38)
|
||
|
||
> 在上一节教程中,我们创建了一个第三方应用,得到了对应的 suite_id/ suite_secret 信息,但是并没有对数据回调和接口回调进行响应,网页提示验证不通过。
|
||
|
||
在上一节教程中,我们创建了一个第三方应用,得到了对应的 suite_id/ suite_secret 信息,但是并没有对数据回调和接口回调进行响应,网页提示验证不通过。
|
||
在本节课程中,我们将完善服务端的逻辑,接收企业微信的回调,使回调 URL 的配置能够通过验证。
|
||
|
||
通过本节教程,开发者将了解:
|
||
|
||
1. 如何接收企业微信的指令和数据回调
|
||
2. 如何对企业微信的回调进行验证
|
||
3. 如何实现第三方应用的回调服务
|
||
|
||
[](#%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C)准备工作
|
||
---------------------------------------------
|
||
|
||
在开始本教程内容前,开发者需要做如下准备:
|
||
|
||
* 已创建第三方应用,并在第三方【应用详情】-【回调配置】找到对应的 Token 和 EncodingAESKey,可参考 [一:如何创建第三方应用](https://developer.work.weixin.qq.com/tutorial/detail/37)。
|
||
* 在回调的实现逻辑中需要进行加解密计算,企业微信已经提供了 C++ Python PHP Java Go C# Node.js 等语言版本的加解密库,并且均提供了解密、加密、验证 URL 三个接口,开发者可以根据开发需要 [下载加解密库](https://developer.work.weixin.qq.com/devtool/introduce?id=10128)。
|
||
* 根据实际开发需要,搭建内网穿透服务。
|
||
|
||
本节教程将以 Node.js 作为代码语言示例,讲解对应的操作流程。
|
||
|
||
[](#%E5%9B%9E%E8%B0%83%E7%9A%84%E5%AE%9E%E7%8E%B0%E7%B1%BB%E5%9E%8B)回调的实现类型
|
||
---------------------------------------------------------------------------
|
||
|
||
在配置应用的开发信息时,我们已经知道了第三方应用的回调配置有数据回调和指令回调两种。
|
||
|
||
**数据回调**,用于接收托管企业微信应用的用户消息、进入应用事件、通讯录变更事件。
|
||
**指令回调**,用于接收应用授权变更事件(应用添加、删除、修改)以及 ticket 参数,ticket 说明详 API 接口说明。
|
||
|
||
对于数据回调和指令回调的两个 URL ,在服务端的实现时,都必须同时支持 HttpGet 以及 HttpPost 两种能力。
|
||
|
||
#### [](#1%E3%80%81get-%E7%B1%BB%E5%9E%8B)1、Get 类型
|
||
|
||
仅用于在应用创建配置应用信息时的验证,企业微信服务端会向回调 URL 发起一个 Get 请求,当该回调 URL 按照约定进行了响应后,表明第三方服务具备解析企业微信推送消息的能力。
|
||
|
||
#### [](#2%E3%80%81post-%E7%B1%BB%E5%9E%8B)2、Post 类型
|
||
|
||
用于实际的业务请求,比如应用菜单的点击事件,用户消息等。当有回调的行为发生时,企业微信服务端会向该回调 URL 发起一个 Post 请求,同时数据会已加密的形式推送到该回调 URL,第三方服务商接受信息、解密信息,处理业务逻辑,并且按照约定进行响应即可。
|
||
|
||
[](#%E5%9B%9E%E8%B0%83%E5%86%85%E7%BD%91%E7%A9%BF%E9%80%8F)回调内网穿透
|
||
-----------------------------------------------------------------
|
||
|
||
回调服务的 URL 是一个公网的 URL,如果在实际进行应用开发时,代码和服务是部署在本地机器的话,那么需要搭建一个内网穿透服务,要让企业微信服务器发来的数据回调和指令回调,能够到达本地服务。
|
||
|
||
三方应用开发的内网穿透,可以使用官方提供的 [内网穿透工具](https://developer.work.weixin.qq.com/devtool/introduce?id=40600) 进行快速调试和验证。
|
||
|
||
目前社区有许多较成熟的内网穿透解决方案,开发者可根据实际需要进行选择。
|
||
|
||
[](#get-%E5%9B%9E%E8%B0%83%E5%AE%9E%E7%8E%B0)Get 回调实现
|
||
-----------------------------------------------------
|
||
|
||
根据创建第三方应用时填写的回调 URL,假设为 `http://myapp.com/callback/command` ,我们将来实现这个 URL 的 get 请求路由,并且按照要求响应。
|
||
|
||
### [](#1%E3%80%81%E5%AE%9E%E7%8E%B0-get-%E8%B7%AF%E7%94%B1)1、实现 get 路由
|
||
|
||
数据回调 URL 和 指令回调 URL 均需要支持 HttpGet 形式的请求回调。
|
||
|
||
```
|
||
const express = require('express');
|
||
const router = express.Router();
|
||
// get方式 用于验证
|
||
router.get('/callback/data', validateCallback);
|
||
router.get('/callback/command', validateCallback);
|
||
```
|
||
|
||
### [](#2%E3%80%81%E8%A7%A3%E6%9E%90%E5%9B%9E%E8%B0%83%E5%8F%82%E6%95%B0)2、解析回调参数
|
||
|
||
企业微信回调服务会通过 HttpGet 的形式请求回调 URL,并将 msg_signature、timestamp、nonce、echostr 以 query 参数的形式传递过来。我们在回调 URL 的逻辑中,解析出这些参数。
|
||
|
||
```
|
||
// 从 query 中获取相关参数
|
||
const { msg_signature, timestamp, nonce, echostr } = req.query;
|
||
```
|
||
|
||
### [](#3%E3%80%81%E8%AE%A1%E7%AE%97%E7%AD%BE%E5%90%8D%E5%B9%B6%E6%A0%A1%E9%AA%8C)3、计算签名并校验
|
||
|
||
根据在上一步获取的参数 timestamp、nonce、echostr,连同 Token 一起,使用加解密库的加密函数重新加密计算签名。
|
||
|
||
```
|
||
// 引入官方的加解密库
|
||
const crypto = require('@wecom/crypto');
|
||
// 在应用详情页找到对应的token
|
||
const token = '';
|
||
// 重新计算签名
|
||
const signature = crypto.getSignature(token, timestamp, nonce, echostr);
|
||
console.log('signature', signature);
|
||
```
|
||
|
||
### [](#4%E3%80%81%E8%A7%A3%E5%AF%86%E5%9B%9E%E8%B0%83%E7%9A%84%E5%8A%A0%E5%AF%86%E4%B8%B2%EF%BC%8C%E5%B9%B6%E5%B0%86%E5%86%85%E5%AE%B9%E8%BF%94%E5%9B%9E)4、解密回调的加密串,并将内容返回
|
||
|
||
通过 EncodingAESKey 和解析出来的 echostr 作为参数,使用加解密库的解密函数,解密出对应的 message,并立即以明文的形式直接返回。
|
||
|
||
```
|
||
// 校验签名是否正确
|
||
if (signature === msg_signature) {
|
||
console.info('签名验证成功')
|
||
// 在应用详情页找到对应的EncodingAESKey
|
||
const aeskey = '';
|
||
// 如果签名校验正确,解密 message
|
||
const { message } = crypto.decrypt(aeskey, echostr);
|
||
console.log('message', message);
|
||
// 返回 message 信息
|
||
res.send(message);
|
||
}
|
||
```
|
||
|
||
到这里,我们已经完成了回调 URL 的 get 实现,在【应用详情】-【回调配置】重新点击编辑保存,便可以重新触发指令回调 URL 和数据回调 URL 的验证,确保网页不再提示 “服务商未响应请求,将无法获取用户事件回调”。
|
||
|
||

|
||
|
||
想要了解更多 Get 回调的实现,可以参考 [支持 http-get 请求验证 url 有效性](https://developer.work.weixin.qq.com/document/path/91116#31-%E6%94%AF%E6%8C%81http-get%E8%AF%B7%E6%B1%82%E9%AA%8C%E8%AF%81url%E6%9C%89%E6%95%88%E6%80%A7)。
|
||
|
||
[](#post-%E5%9B%9E%E8%B0%83%E5%AE%9E%E7%8E%B0)Post 回调实现
|
||
-------------------------------------------------------
|
||
|
||
### [](#1%E3%80%81%E5%AE%9E%E7%8E%B0-post-%E8%B7%AF%E7%94%B1)1、实现 post 路由
|
||
|
||
数据回调 URL 和 指令回调 URL 均需要支持 HttpPost 形式的请求回调。
|
||
|
||
```
|
||
const express = require('express');
|
||
const router = express.Router();
|
||
// post方式 用于授权、业务数据回调
|
||
router.post('/callback/command', function (req, res, next) {});
|
||
```
|
||
|
||
### [](#2%E3%80%81%E8%A7%A3%E6%9E%90%E5%8F%82%E6%95%B0%EF%BC%8C%E5%B9%B6%E6%A0%A1%E9%AA%8C%E7%AD%BE%E5%90%8D)2、解析参数,并校验签名
|
||
|
||
企业微信回调服务会通过 HttpPost 的形式请求该回调 URL,并将 msg_signature、timestamp、nonce 以 query 参数的形式传递过来,像 HttpGet 形式一样,需要先对参数进行解析,并且校验签名有效。
|
||
获取的参数 timestamp、nonce、echostr,连同 Token 一起,使用加解密库的加密函数重新加密计算签名。
|
||
|
||
```
|
||
// 引入官方的加解密库
|
||
const crypto = require('@wecom/crypto');
|
||
// 从 query 中获取相关参数
|
||
const { msg_signature, timestamp, nonce } = req.query;
|
||
// 在应用详情页找到对应的token
|
||
const token = '';
|
||
// 重新计算签名
|
||
const signature = crypto.getSignature(token, timestamp, nonce, echostr);
|
||
console.log('signature', signature);
|
||
```
|
||
|
||
### [](#3%E3%80%81%E8%A7%A3%E5%AF%86%E5%AF%86%E6%96%87%E6%B6%88%E6%81%AF)3、解密密文消息
|
||
|
||
在 HttpPost 回调中,密文内容是以 XML 字符串的形式放在 RawBody 中,通过对该 XML 字符的解析读取出对应的加密消息 Encrypt。
|
||
通过 EncodingAESKey 和加密消息 Encrypt 作为参数,使用加解密库的解密函数,解密出对应的 message。
|
||
而 message 仍然是一个 XML 字符串格式,再次对该 XML 解析,便可以得到本次 HttpPost 回调实际的数据信息。
|
||
|
||
```
|
||
// 解析 xml 的字符串内容
|
||
let wholeXML = await getRawBody(req, {
|
||
length: req.headers['content-length'],
|
||
limit: '1mb',
|
||
encoding: 'utf-8'
|
||
});
|
||
// 将 xml 解析成 json
|
||
let formatJson = await parseXML(wholeXML);
|
||
// 得到加密消息体
|
||
let encrypt = formatJson.Encrypt;
|
||
// 在应用详情页找到对应的EncodingAESKey
|
||
const aeskey = '';
|
||
// 将加密消息体进行解密,解密后仍旧是 xml 字符串
|
||
let messageXML = crypto.decrypt(aeskey, encrypt);
|
||
// 把解密后 xml 消息体字符串,解析成 json
|
||
let callbackDataBody = await parseXML(messageXML.message);
|
||
console.log('CallbackData', callbackDataBody);
|
||
```
|
||
|
||
### [](#4%E3%80%81%E5%93%8D%E5%BA%94%E6%9C%AC%E6%AC%A1%E8%AF%B7%E6%B1%82)4、响应本次请求
|
||
|
||
不同的业务回调要求返回不同内容,在本节中我们直接返回 `success` 字符串即可。
|
||
|
||
想要了解企业微信回调设置的更详细信息,可以参考文档 [回调配置](https://developer.work.weixin.qq.com/document/path/91116)。
|
||
|
||
到这里,我们已经完成了第三方应用的 HttpPost 回调配置逻辑框架,在下一节教程,我们就可以利用这个指令回调实现 suite_ticket 的接收,从而获得 suite_access_token。 |