今天给把微信后台从原来的 http 升级成 https 了,升级完成后需要修改微信公众号后台配置的 url,和应用程序中配置的 url,以及调用微信 API 更新公众号菜单。
staging 环境中使用的是微信的测试号,更新菜单成功。更新 production 环境的菜单时就提示 {"errcode":40001,"errmsg":"invalid credential, access_token is invalid or not latest hints: [TKNa1CPce-8MnYoA!]"}
。这个错误表明更新菜单 API 中传入的 access_token 不对。
直接调用 access_token 接口提示 {"errcode":40164,"errmsg":"invalid ip 223.75.224.97 ipv6 ::ffff:223.75.224.97, not in whitelist hint: [4IP_rA04044809]"}
,多次调用这个接口,输出的错误信息中的 IP 地址还不一样。这表明服务器的出口 IP 一直在变,而微信 API 只允许 IP 白名单列表中的服务器 IP 访问,如果出口 IP 不停改变,则无法配置 IP 白名单列表,微信服务就无法正常使用。
说明一下,我们的微信后台是部署在学校内网的一个 Web 服务,使用一台云服务器做内网穿透,向公网提供服务。该服务已经正常运行多年,不过一直都提供的是 http 服务。今天将 http 服务升级为 https 的时候需要修改配置并重启,重启后才出现的问题。
内网服务器出口 IP 一直都是固定的,用我们的公网服务器可以证实这个出口 IP 没变。那么问题就可能出现在 dns 上,可能是微信服务器配置了 dns 负载均衡,同时使用了教育网、移动、联通、电信等多个网络线路上的 IP。我们的出口 IP 并不是公网 IP,是学校局域网的一个出口 IP,可能只属于教育网线路,当流量走其他线路的时候,出口 IP 就不一样了。
如何解决
我们的 IP 白名单列表里配置了两个 IP,一个是内网服务器的出口 IP,另一个是云服务器的公网 IP。由于云服务器的公网 IP 稳定,在云服务器上总是可以正常访问微信的接口。因此可以在云服务器上调试。
在云服务器上使用 curl -v
访问微信 API,可以输出微信服务器的 IP 地址和响应内容。根据响应内容,我们可以判断访问成功。
curl 'https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=<appid>&secret=<secret>' -v
* Trying 58.251.80.204...
{"access_token":"<access_token>","expires_in":7200}
在内网服务器上使用同样方法访问微信 API,输出了不同的 IP 地址,从响应内容中可以看出访问失败。
* Trying 117.184.242.111... connected
{"errcode":40164,"errmsg":"invalid ip 223.75.224.200 ipv6 ::ffff:223.75.224.200, not in whitelist hint: [5aDU5a05392994]"}
把公网服务器上成功访问的 IP,写进内网服务器的 hosts 文件里,再次访问,访问成功。这可以表明,确实是 DNS 问题,访问某些 IP 的时候我们的出口 IP 不会改变,访问另一些 IP 的时候我们的出口 IP 会改变,前者才是我们应该访问的 IP。
目前我采用的解决方案是,把成功访问的 IP 写进内网服务器的 hosts 文件里(在 /etc/hosts
中添加一行 58.251.80.204 api.weixin.qq.com
),并且要确保 nsswitch 配置中 hosts 文件先于 dns 解析。
经测试,当前可用的 IP 地址有 58.251.80.204, 163.177.83.164。
目前这个方案只能算 work around,并没有真正地解决问题,也不知道微信的 IP 地址稳不稳定,如果稳定这个问题就算解决了,如果不稳定就得另寻他法了。
另外,之前没有出现过这种问题,升级为 https 后就出现了,我想这或许也有一点关系,有待后续研究。(但是 https 是 nginx 里配置的啊,对后端服务应该没影响。%>_<%)