微信公众号(服务号)服务器对接踩坑记录
接入前配置与首次验签
在公众号后台「基本配置」里填好服务器 URL、Token、EncodingAESKey,并勾选消息加解密方式(明文 / 兼容 / 安全模式)。提交配置时,微信会 GET 一次回调地址,带上 signature、timestamp、nonce、echostr 四个参数做校验:
- 将 Token、timestamp、nonce 三个字符串按字典序排序后拼接成一个字符串;
- 对该字符串做 SHA1;
- 结果与
signature比对,一致则原样返回echostr完成接入。
注意:字典序比较按字符逐字节进行,不是按字符串长度比较。
消息与事件推送
用户发消息或触发事件后,微信 POST 到服务器:
- 明文模式:请求体是 XML,直接读。
- 安全模式(推荐):消息体需要解密后再处理,响应的消息也要加密。
安全模式加解密(AES-256-CBC)
EncodingAESKey 与密钥
EncodingAESKey 是 43 位 Base64 字符串,解密密钥为:
key = Base64_Decode(EncodingAESKey + "=")
IV(初始向量)取解密后密钥的前 16 字节。
解密
AES-256-CBC 解密后的明文包结构:
random(16字节随机串) + msg_len(4字节网络字节序) + 消息体XML + AppID
解密步骤:
- AES-256-CBC 解密;
- 去掉 PKCS7 padding(padding 每个字节的值等于补位长度);
- 取后 4 字节网络字节序得到
msg_len; - 按
msg_len截出消息体 XML; - 校验尾部 AppID 是否与自己的 AppID 一致,防止伪造。
加密
加密为解密的反过程:
- 拼接
random + msg_len + xml + appid; - 按 PKCS7 补位到 32 的倍数;
- CBC 加密后 Base64 输出。
有两个容易被攻击的点:
- padding 校验不要泄露具体错误信息,使用常量时间比较,防止 oracle padding 攻击;
- AppID 校验失败直接拒绝,不要返回详细异常。
验签与调试建议
- 签名比对用
hash_equals(PHP)或同类常量时间比较函数,不要用==。 - 本地联调别直接拿微信后台触发推送,先用官方「消息加解密库」的各语言 demo 把加解密跑通,再对接业务。
相关文档:
access_token 缓存与刷新
access_token 有效期 7200 秒,必须自己缓存(Redis / 内存均可),不能每次请求都现调接口获取。
官方建议提前 5 分钟左右刷新。多实例部署时注意缓存击穿:加分布式锁,只让一个实例去拉取新 token,其他实例等待并复用新 token。
过期时调用接口返回 errcode: 40001 invalid credential,此时应清理缓存并重试一次。
被动回复的超时与幂等
微信要求收到消息后 5 秒内回复,否则会重试 3 次。回复内容为明文或加密 XML,Content 节点用 CDATA 包裹。
业务处理耗时(查库、调第三方 API)时,正确姿势:
- 先把消息落队列 / 异步处理;
- 快速返回空串或转发到客服。
否则超时后微信重试会导致重复下单 / 重复发送——这是被动回复最容易踩的幂等坑。