目录

一、前言:

二、配置微信公众号基础接口

(1)填写IP白名单和App Secret

(2)配置微信公众号服务器URL​编辑

(3)配置微信公众号网页授权域名

(4)自定义菜单

(4)微信公众号推送的消息是xml

(5)获取access_token的函数

(6) 将binary转为16进制字符串的函数

三、明文模式

(1)验证消息安全签名

(2)被动解析用户消息

四、密文模式

(1)验证消息安全签名

(2)解析密文消息为明文

(3)加密明文消息为密文


一、前言:

目前微信公众号开放平台上面,关于被动解析用户消息的安全模式的接口示例,只有C/C++/PHP/Java/Python,因此我在阅读了这几份接口文档之后,写了一份适合erlang接入的模块(其实就是我觉得官网这是无视我们大erlang群体吗——(〃>目<))。这里不讲兼容模式,因为兼容模式其实也是明文模式和安全模式都有的一个模式。

erlang 版本 OTP 25,不同于这版本的话,有些函数方法是不一定兼容的,因此请谨慎。

二、配置微信公众号基础接口

(1)填写IP白名单和App Secret

之后创建自定义菜单时,需要获取access_token,而获取access_token则需要AppID和App Secret。不过现在微信公众平台已经不会主动储存App Secret,因此需要我们开发者妥善保管好。

(2)配置微信公众号服务器URL

之后微信公众号消息都会推送到这个服务器地址URL上,但是不会有QueryString,因此填写的URL也需要谨慎。

(3)配置微信公众号网页授权域名

之后微信公众号的图文网页指定的存放域名就是这里,但是可以填写多个,因此不用担心文件管理问题。

(4)自定义菜单

自定义菜单,其实不算很麻烦,官网的文档理解起来还不算太难。需要注意的是,这里只接受json格式的自定义菜单。

{"button": [{"name": "扫码", "sub_button": [{"type": "scancode_waitmsg", "name": "扫码带提示", "key": "rselfmenu_0_0", "sub_button": [ ]}, {"type": "scancode_push", "name": "扫码推事件", "key": "rselfmenu_0_1", "sub_button": [ ]}]}, {"name": "发图", "sub_button": [{"type": "pic_sysphoto", "name": "系统拍照发图", "key": "rselfmenu_1_0", "sub_button": [ ]}, {"type": "pic_photo_or_album", "name": "拍照或者相册发图", "key": "rselfmenu_1_1", "sub_button": [ ]}, {"type": "pic_weixin", "name": "微信相册发图", "key": "rselfmenu_1_2", "sub_button": [ ]}]}, {"name": "发送位置", "type": "location_select", "key": "rselfmenu_2_0"},{"type": "media_id", "name": "图片", "media_id": "MEDIA_ID1"}, {"type": "view_limited", "name": "图文消息", "media_id": "MEDIA_ID2"},{"type": "article_id","name": "发布后的图文消息","article_id": "ARTICLE_ID1"},{"type": "article_view_limited","name": "发布后的图文消息","article_id": "ARTICLE_ID2"}]
}

(4)微信公众号推送的消息是xml

xml格式如下(官网可查):

<xml>
<ToUserName><![CDATA[toUser]]></ToUserName>
<FromUserName><![CDATA[FromUser]]></FromUserName>
<CreateTime>123456789</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[CLICK]]></Event>
<EventKey><![CDATA[EVENTKEY]]></EventKey>
</xml>

PS:前面的这些步骤,其实官网都可查,只是怕不细心的小伙伴没查就来看这篇文章,容易迷糊。链接在此。

(5)获取access_token的函数

此处再附上,获取access_token的函数,因为接下来介绍的方法中会用上:

%% 获取公众号access_token
get_oa_access_token() ->NowTime = util:unixtime(),Method = post,{ok, AppID} = api_sys:get_oa_app_id(), %% AppID{ok, Secret} = api_sys:get_oa_secret(), %% App SecretURL = "https://api.weixin.qq.com/cgi-bin/token?"++ "&appid=" ++ AppID++ "&secret=" ++ Secret++ "&grant_type=client_credential",Header = [],Type = "application/json; encoding=utf-8",Body = "",HTTPOptions = [],Options = [],case httpc:request(Method, {URL, Header, Type, Body}, HTTPOptions, Options) of{ok, {{_, 200, "OK"}, _, Return}} ->case jsx:decode(erlang:list_to_binary(Return)) of#{<<"access_token">> := AccToken} ->{ok, AccToken};Error ->?ERR("请求微信接口调用凭证失败 ~p~n", [Error]),errorend;Error ->?ERR("请求微信接口调用凭证失败 ~p~n", [Error]),errorend.

(6) 将binary转为16进制字符串的函数

此处再附上,将binary转为16进制字符串的函数,因为接下来介绍的方法中会用上:

%% 二进制转16进制
binary_to_hex(Bin) ->binary_to_list(iolist_to_binary([io_lib:format("~2.16.0b", [A]) || A <- binary_to_list(Bin)])).

三、明文模式

(1)验证消息安全签名

消息安全签名的验证数据是存放在QueryString中,即Http/Https中的qs,这里将qs格式转化为map格式。明文模式的map格式大致如下:

#{<<"nonce">> => <<"123123435">>,<<"openid">> => <<"test_asda123123sdas123">>,<<"signature">> => <<"sdasdasd2341232134dfqw534tvfg456tgvd">>,<<"timestamp">> => <<"1231245254">>
}

只有验证了消息安全签名准确,才能说明得到的消息可信。验证安全签名的函数如下:

get_oa_msgs_sign(#{<<"nonce">> := Nonce,<<"signature">> := Sign,<<"timestamp">> := TimeStamp} = _QSMap, _BodyMap) ->{ok, Token} = api_sys:get_oa_token(), %% 配置服务器URL的token令牌List = lists:concat(lists:sort([?IF(erlang:is_binary(A), erlang:binary_to_list(A), A) || A <- [Token, TimeStamp, Nonce]])),ShaSign = util:binary_to_hex(crypto:hash(sha, List)),erlang:binary_to_list(Sign) == ShaSign.

(2)被动解析用户消息

从上面的知识,其实我们知道微信公众号推送到服务器的用户消息是xml格式,转化为map格式,大概如下:

#{<<"xml">> =>#{<<"CreateTime">> => <<"1673589909">>,<<"Event">> => <<"CLICK">>,<<"EventKey">> => <<"test">>,<<"FromUserName">> => <<"test_dasdasdasdasd">>,<<"MsgType">> => <<"event">>,<<"ToUserName">> => <<"gh_12312dasdas">>}
}
上面这xml格式不是所有的消息都是这种格式,具体可以看这里。
而这里我们也简单用回复用户纯文本的方式,写了个测试函数,如下:
%% FromUserID : xml中FromUserName
get_oa_auto_response_click_text(FromUserID, Response) ->BodyMap = #{<<"touser">> => unicode:characters_to_binary(FromUserID, utf8),<<"msgtype">> => <<"text">>,<<"text">> => #{<<"content">> => unicode:characters_to_binary(Response, utf8)}},do_oa_auto_response_sender(jsx:encode(BodyMap)).do_oa_auto_response_sender(BodyData) ->{ok, AccToken} = get_oa_access_token(), %% 获取access_tokenMethod = post,URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?"++ "&access_token=" ++ AccToken,Header = [],Type = "application/json; encoding=utf-8",HTTPOptions = [],Options = [],case httpc:request(Method, {URL, Header, Type, BodyData}, HTTPOptions, Options) of{ok, {{_, 200, "OK"}, _, Return}} ->Return;Error ->?ERR("自动回复失败...~p~n", [Error]),<<>>end.

四、密文模式

公众号在正式运营的情况下,为了避免公众号消息被有心人监听破解,因此,我们会选择微信提供的消息密文模式进行消息监听,但是这部分也是是比较麻烦且让人头疼的部分,因为官网并没有相关代码模块,其他语言示例中的函数方法也跟erlang提供的函数命名差距较大,因此此处是我近期整理的函数模块,希望对大家有所帮助。

同样再次强调一下,我用的erlang版本是OTP 25,其他的版本分支的函数会有所出入,请大家参考的时候多研究一下接口说明。

(1)验证消息安全签名

密文模式下的安全签名验证方式与明文模式下的不同,具体可以看如下代码,同样将qs格式转化为map格式:

%% 验证公众号消息安全签名
get_oa_msgs_sign(#{<<"msg_signature">> := MgsSign,<<"nonce">> := Nonce,<<"timestamp">> := TimeStamp} = _QSMap,#{<<"Encrypt">> := Encrypt} = _BodyMap) ->{ok, Token} = api_sys:get_oa_token(), %% 配置服务器URL的token令牌List = lists:concat(lists:sort([?IF(erlang:is_binary(A), erlang:binary_to_list(A), A) || A <- [Token, TimeStamp, Nonce, Encrypt]])),ShaSign = util:binary_to_hex(crypto:hash(sha, List)),erlang:binary_to_list(MgsSign) == ShaSign;

细心的小伙伴就会发现,密文模式下的验证字段是 msg_signature ,而明文模式下,则验证字段是 signature。

(2)解析密文消息为明文

废话不多说,上代码,如下:

%% 解密公众号密文消息
get_oa_decrypt_msgs(EncryptMsgs) ->%% 配置服务器URL时,消息加解密密钥(EncodingAESKey){ok, OldAesKey} = api_sys:get_oa_aes_key(),%% 密钥 ++ "=",然后转为binary,再base64解码,得到新的AesKeyAesKey = base64:decode(unicode:characters_to_binary(OldAesKey ++ "=")),%% 新的AesKey的前16位是aes加密需要用的 iv值<<IV:16/binary, _/binary>> = AesKey,%% 用base64将密文进行解码,得到新的密文PlainTextPlainText = base64:decode(EncryptMsgs),%% 函数选择OTP 25 版的crypto:crypto_one_time/5,解密方式用aes_cbc%% 中间三个参数AesKey, IV, PlainText已在前面已经获得了%% 最后一个参数,解密用false,加密则用true%% 得到全部解密后的明文DecryptTextDecryptText = crypto:crypto_one_time(aes_cbc, AesKey, IV, PlainText, false),%% 明文需要剔除后面的明文补位字符ContentBin = get_oa_pkcs7_decoder(DecryptText),%% 然后前面是补位16位的随机字符,和4位的正确明文长度,由此可得正确的明文长度XmlContentLen<<XmlContentLen:32>> = binary:part(ContentBin, 16, 4),%% 截取20位之后,到XmlContentLen的明文内容XmlContent = binary:part(ContentBin, 20, XmlContentLen),{ok, XmlContent}.%% 获取删除补位字符的明文
get_oa_pkcs7_decoder(DecryptText) ->%% 最后一位是明文最后的补位字符长度BlockSize = 32,Pad = binary:last(DecryptText),%% Pad小于1或者大于32位,Pad=0,反之不变NPad = ?IF(Pad < 1 orelse Pad > BlockSize, 0, Pad),ContentLen = erlang:byte_size(DecryptText),%% 剔除后面的明文补位字符ContentBin = binary:part(DecryptText, 0, ContentLen - NPad),ContentBin.

(3)加密明文消息为密文

%% 加密公众号明文消息
get_oa_encrypt_msgs(DecryptMsgs) ->%% 随机16位字符,用于填充在密文前面RandomText = util:rand_list_repeat(16, "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM"),%% 配置服务器URL时,消息加解密密钥(EncodingAESKey){ok, OldAesKey} = api_sys:get_oa_aes_key(),%% 密钥 ++ "=",然后转为binary,再base64解码,得到新的AesKeyAesKey = base64:decode(unicode:characters_to_binary(OldAesKey ++ "=")),%% 新的AesKey的前16位是aes加密需要用的 iv值<<IV:16/binary, _/binary>> = AesKey,%% 公众号的AppID{ok, AppID} = api_sys:get_oa_app_id(),%% 密文填充顺序:randomStr + textLen + text + appidRandomTextBin = unicode:characters_to_binary(RandomText),DecryptMsgsBin = unicode:characters_to_binary(DecryptMsgs),DecryptMsgsBinLen = erlang:byte_size(DecryptMsgsBin),AppIDBin = unicode:characters_to_binary(AppID),%% 得到没加密的明文binaryUnBin = <<RandomTextBin/binary, DecryptMsgsBinLen:32, DecryptMsgsBin/binary, AppIDBin/binary>>,BinCount = erlang:byte_size(UnBin),%% 获取补位字符PKCS7Bin = get_oa_pkcs7_encoder(BinCount),%% 明文后面填充补位字符PlainText = <<UnBin/binary, PKCS7Bin/binary>>,%% 函数选择OTP 25 版的crypto:crypto_one_time/5,加密方式用aes_256_cbc,如果用aes_cbc,会提示找不到加密类型%% 中间三个参数AesKey, IV, PlainText已在前面已经获得了%% 最后一个参数,解密用false,加密则用true%% 得到全部加密后的密文EncryptMsgsEncryptMsgs = crypto:crypto_one_time(aes_256_cbc, AesKey, IV, PlainText, true),%% 最后再将密文EncryptMsgs进行base64加码,得到最终的密文Base64EncryptMsgsBase64EncryptMsgs = base64:encode(EncryptMsgs),{ok, Base64EncryptMsgs}.%% 获取补位字符
get_oa_pkcs7_encoder(Count) ->BlockSize = 32,%% 32 - 明文长度除以32取余,如果余数=0,则补位字符位数=32,反之不变Pad = BlockSize  - (Count rem BlockSize),NPad = ?IF(Pad == 0, BlockSize, Pad),%% 复制补位字符,长度为前面得到的补位字符位数NPad,复制所用的字符则是补位字符位数所对应的字符PadStr = lists:duplicate(NPad, NPad),erlang:list_to_binary(PadStr).

最后怎么回复用户消息,其实跟明文模式差不多。

超详细 erlang服务器之微信公众号被动解析用户消息(明文模式安全模式)相关推荐

  1. 微信公众平台——被动回复用户消息

    微信公众平台--被动回复用户消息 开发模式下的回复信息基础接口,可用来向用户回复文本消息.图片消息.语音消息.视频消息.小视频消息.地理位置消息.链接消息. 1.回复文本消息 function Rep ...

  2. php微信公众号开发难吗,PHP微信公众号开发的两种模式

    微信现在已经深入到每个家庭,每个人至少人手一个账号是确定的,所以开发公众号也是很普遍的,今天爱站技术频道小编为大家带来的是PHP微信公众号开发的两种模式,感兴趣的小伙伴们可以参考下面的介绍. 学习步骤 ...

  3. php公众号被动回复,微信公众号被动消息回复原理解析

    背景:某分厂需要实时查询工件堆放的位置,要求快速便捷,因此设计了采用微信公众号被动回复信息的方案. 技术实现:开发者服务器--基于Angular2框架的已发布网站,编程语言为Python,后台存储数据 ...

  4. 微信公众号网页在本地开发模式下如何使用正式环境的域名来调试

    微信公众号网页在本地开发模式下如何使用正式环境的域名来调试? 鄙人之前也不知道,网上搜了一下,看到的几篇文章都是要使用代理,有用Nginx的,还有自己写代理的.主要是按照步骤做了并不行.于是自己折腾了 ...

  5. 玩转微信 为您推荐十大生活服务类微信公众号

    微信,超越了通讯工具后,它将如何丰富你的移动生活? 你在用微信吗?这个拥有超2亿用户的移动应用已不仅仅是个"通讯工具",它正在让你的移动生活变得更加有趣.但完成这个使命的除了微信本 ...

  6. 微信公众号开发之成为开发者模式

    微信开发交流群:148540125 欢迎留言.转发 项目源码参考地址 点我点我–欢迎Start 项目如何导入到IDE并启动参考文章 本文将学习到: 1.如何开发调试微信公众号 2.如何开启开发者模式 ...

  7. 微信公众号被动回复方案梳理

    微信公众号机器人客服回复方案预梳理 一.微信公众平台开发接入指南 接入微信公众平台开发,需要按照如下步骤完成: 1.填写服务器配置 登录微信公众平台官网后,在公众平台后台管理页面 - 开发者中心页,点 ...

  8. 微信公众号商业化有哪些盈利模式?

    当你做微信公众号的时候在最后变现时期, 会有很多的盈利模式,结合不同的用户群特点, 盈利模式可以划分为以下几种: A:给互联网行业相关公司推送软文广告 B:帮互联网公司发布活动信息收取信息 C:自己组 ...

  9. 微信公众号被动消息回复实现

    引言 公众号经常会有一些自动回复,最近顺便研究了一下公众号被动回复的代码实现,并不是特别难:下面我用代码演示一下: 注:公众号的一些准备工作已经在<获取公众号二维码>一文中说过了,这里就不 ...

最新文章

  1. C# API中的模型和它们的接口设计
  2. package中的常用script命令
  3. Linux查看进程的线程信息
  4. 仿京东首页上侧导航左侧地址栏布局(1)
  5. 机器学习/梯度下降算法
  6. Kettle常用的配置文件
  7. Angular CLI的安装和使用
  8. logincontroller.java_ucenter整合java项目,注册问题
  9. matlab大作业题题单,2011MATLAB大作业-题目-
  10. 8月20日全球六大国际域名解析量变化情况统计报告
  11. android 设备标识
  12. OpenStack日志搜集分析之ELK
  13. v8go 库手动编译 v8 golang 库手动编译
  14. matlab画直方图_直方图规定化+暗通道去雾 python
  15. Windows服务器配置fileZilla Server
  16. php中ob函数的用法
  17. IT学生解惑真经(转) (真的好经典!)
  18. 云服务器出现502错误的原因与解决方案
  19. 解锁system分区
  20. 靠谱的软件外包平台有哪些?

热门文章

  1. 华为历史的几次重大管理变革
  2. 【 D3.js 入门系列 --- 10.2 】 你可以拖动地图
  3. arraycopy - 数组复制【详细图解】
  4. 微信协议网页版微信协议解析
  5. JVM---G1中的RSet和卡表
  6. jdbc,叫的很顺口,但是你真的知道这个是什么意思吗?
  7. uniapp小程序使用RSA加密解密
  8. 索引(index)_普通索引、唯一索引和复合索引.索引查询
  9. 【uniapp】结构
  10. linux下编写播放DVD的软件相关库