狐友们,万万不可掉队,VFP开发企业微信第一关回调该怎么配?
加菲猫在前面的公众号开发中已经教过VFP怎么回复用户发过来的消息,这回复就是回调功能。那企业微信的回调就比公众号的回调多了一步,那就是加解密。作者:福建小王(VFP MIX ALL 社群)
开发环境:
1.VFP SP2 7423
2.祺佑三层开发框架
3.vfpencryption71.fll 道哥开发的加解密库
有需要这款加解密库的可以关注公众号:加菲猫的VFP发送"加解密"的文字即可获得。
一、企业微信回调背景介绍:
在集成企业微信与内部系统时,我们往往需要搭建一个回调服务。回调服务,可以实现:
回调示意图
自定义丰富的服务行为。比如,用户向应用发消息时,识别消息关键词,回复不同的消息内容;用户点击应用菜单时,转化为指令,执行自动化任务。
可以及时获取到状态变化。比如,通讯录发生变化时,不需要定时去拉取通讯录对比,而是实时地获取到变化的通讯录结点,进行同步。
二、回调服务配置
配置回调服务,需要有三个配置项,分别是:URL,Token和EncodingAESKey。
首先,URL为回调服务地址,由开发者搭建(需要满足接口要求,参考第3部分说明),用于接收通知消息或者事件。
其次,Token用于计算签名,由英文或数字组成且长度不超过32位的自定义字符串。开发者提供的URL是公开可访问的,这就意味着拿到这个URL,就可以往该链接推送消息。那么URL服务需要解决两个问题:
如何分辨出是否为企业微信来源?
如何分辨出推送消息的内容是否被篡改?
通过数字签名就可以解决上述的问题。具体为:约定Token作为密钥,仅开发者和企业微信知道,在传输中不可见,用于参与签名计算。企业微信在推送消息时,将消息内容与Token计算出签名。开发者接收到推送消息时,也按相同算法计算出签名。如果为同一签名,则可信任来源为企业微信,并且内容是完整的。
如果非企业微信来源,由于攻击者没有正确的Token,无法算出正确的签名;
如果消息内容被篡改,由于开发者会将接收的消息内容与Token重算一次签名,该值与参数的签名不一致,则会拒绝该请求。
最后,EncodingAESKey用于消息内容加密,由英文或数字组成且长度为43位的自定义字符串。由于消息是在公开的因特网上传输,消息内容是可被截获的,如果内容未加密,则截获者可以直接阅读消息内容。若消息内容包含一些敏感信息,就非常危险了。EncodingAESKey就是在这个背景基础上提出,将发送的内容进行加密,并组装成一定格式后再发送。
三、回调服务实现功能
配置回调服务时,需要能同时支持HttpGet以及HttpPost两种能力,
调用
企业微信会先判断URL服务是否具备解析企业微信推送消息的能力。
具体方式是,企业微信往URL服务上发一条Get请求带签名及密文参数到URL服务上,如果URL服务检查签名通过,并能正确返回密文参数对应的明文字符串,则验证通过。此时在企业微信的配置就开始生效。
后续的业务请求(比如应用菜单的点击事件,用户消息等),都会类似的方式(签名+密文)向服务URL推送消息。URL服务验证签名通过后,需要将POST数据解密,就可以得到对应的业务消息明文。
四、开始开发
通讯录同步设置接收事件服务器
在企业微信官网先注册账号,搭建好测试服务器,提前获取corpid、corpsecret和access_token。
在官网通讯录同步模块中,设置接收事件服务器,网址为https://work.weixin.
填写测试服务器URL,Token(可以随机获取)和EncodingAESKey(可以随机获取)。如下图所示
配置
注:URL为测试服务器的URL,用于测试通讯录同步中的回调测试,Token用于测试服务器消息签名体校验,EncodingAESKey用于测试服务器加密字符串的解密。
后端VFP开始开发,关键代码解析
本次实操采用的是加菲猫的猫框,猫框已封装相关过程函数类等可以直接调用,操作非常简便。详细步骤如下:
读取回调token和EncodingAESKey
m.lccorpid=This.appid &&corpid,来自企业微信
m.lctoken=This.urlToken &&回调token,企业微信中定义好
m.lcEncodingAESKey=This.EncodingAESKey&&EncodingAESKey,企业微信中定义好
注:由于采用类封装,将corpid、token和EncodingAESKey都存储到类的属性中。具体测试时可以用变量或者直接赋值。
获取企业微信传递的字符串
m.lcTimestamp=httpqueryparams("timestamp",This.iconnid)
m.lcnonce=httpqueryparams("nonce",This.iconnid)
m.lcechostr=httpqueryparams("echostr",This.iconnid)
注:httpqueryparams()为猫框封装函数,直接调用获取HTTP传递过来的字符串信息。
参数说明:
参数 类型 说明
msg_signature String 企业微信加密签名,msg_signature计算结合了企业填写的token、请求中的timestamp、nonce、加密的消息体。签名计算方法参考 消息体签名检验
timestamp Integer 时间戳。与nonce结合使用,用于防止请求重放攻击。
nonce String 随机数。与timestamp结合使用,用于防止请求重放攻击。
echostr String 加密的字符串。需要解密得到消息内容明文,解密后有random、msg_len、msg、receiveid四个字段,其中msg即为消息内容明文
为了让企业确认调用来自企业微信,企业微信在回调给接收消息url时会带上消息签名,以参数msg_signature标识,企业需要验证此参数的正确性后再解密。
token、timestamp、nonce、echostr这四个参数按照字典序排序,并拼成一个字符串
Dimension laAnswer_url(4)&&定义成数组
laAnswer_url(1)=m.lctoken
laAnswer_url(2)=m.lcTimestamp
laAnswer_url(3)=m.lcnonce
laAnswer_url(4)=m.lcechostr
Asort(laAnswer_url)&&对数组进行排序
m.lcstrcheck=laAnswer_url(1)+laAnswer_url(2)+laAnswer_url(3)+laAnswer_url(4)
注意:Asort()是按升序或降序对数组中的元素排序。这里只针对token、timestamp、nonce、echostr四个变量的值进行排序(即排序的顺序随机,不一定是echostr排一个,token排最后),而不是“token”、“timestamp”、“nonce”和“echostr”按照字典排序,不能搞混。
对该字符串进行sha1计算得到签名
了解明文msg的加密过程
明文字符串由16个字节的随机字符串、4个字节的msg长度、明文msg和receiveid拼接组成。其中msg_len为msg的字节数,网络字节序;sReceiveId 在企业应用的回调,表示corpid
rand_msg = random(16B) + msg_len(4B) + msg + receiveid
对明文字符串加密并Base64编码
msg_encrypt = Base64_Encode(AES_Encrypt(rand_msg))
将明文字符串AESKey加密后,再进行Base64编码,即获得密文msg_encrypt。
开始解密消息
SET LIBRARY TO (LOCFILE("vfpencryption71.fll","FLL")) ADDITIVE &&加载加密解密库
m.lcaes_msg = Strconv(m.lcechostr,14)&&对密文base64解码
m.lcAESKey = Strconv(m.lcEncodingAESKey+ "=",14)&&AESKey先解码,用于AES解密
m.lcIV = Left(m.lcAESKey ,16)&&取lcAESKey 前16个字符串,该值结果为corpid
m.lcrand_msg =Decrypt(m.lcaes_msg , m.lcAESKey , 2, 1, 0, 32, 16, m.lcIV)&&解密后为16进制字符串
RELEASE LIBRARY [vfpencryption71] &&内存中移除库
m.lcrand_msg=Right(m.lcrand_msg,Len(m.lcrand_msg)-16)&&去掉前16随机字节
m.lcmsg_len =Left(m.lcrand_msg,4)&&取出4字节的msg_len
m.lnmsg_len=ConverNum(Strconv(m.lcmsg_len,15),16)&&将lcmsg_len先转成16进制字符串,然后用Myfll函数ConverNum转换为数值型
m.lcmsg=Substr(m.lcrand_msg,5,m.lnmsg_len)&&得到解密后的明文消息
m.lcreceiveid=Substr(m.lcrand_msg,5+m.lnmsg_len)&&得到解密后的corpid
根据明文中的MsgType可知,此为应用消息回调,因此receiveid应该为corpid,对比receiveid与corpid是否一致。
将解密获得的明文消息返回到企业微信,完成通讯录同步的接收事件服务器回调设置。
最终结果返回:
Return m.lcmsg
点击保存,就会显示保存成功,说明配置成功啦。
配置成功
[此贴子已经被作者于2021-12-1 09:17编辑过]