公众号接入至少要分开看三层:服务器地址验证、后续消息事件接收、具体接口权限。配置保存成功,不代表所有接口都已经授权;拿到 access_token 也不等于每项调用都被允许。

下面以 2018 年项目的明文接入流程说明这些关系。安全模式的加解密与当前账号能力应按实际配置处理,不可把这段明文示例用于所有模式。

GET 验证里的 Token 不是 access_token

旧流程的 GET 请求带 signature、timestamp、nonce 和 echostr。服务端将自己配置的 Token、timestamp、nonce 排序拼接后计算 SHA-1,与签名比较,通过后原样返回 echostr。

Token 是服务器配置中约定的校验值;access_token 是调用平台接口的凭证;AppSecret 又是另一类应用凭据。三者不能互换,也不应直接写入公开代码或打印在日志中。

以下是 Java 8 的明文校验函数,Token 从受控环境取得:

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public final class PlainCallbackVerifier {
    public static boolean verify(String token, String timestamp,
                                 String nonce, String signature) {
        if (token == null || token.isEmpty() ||
                timestamp == null || !timestamp.matches("[0-9]{1,20}") ||
                nonce == null || nonce.isEmpty() || nonce.length() > 256 ||
                signature == null || !signature.matches("[0-9a-fA-F]{40}")) {
            return false;
        }
        String[] parts = {token, timestamp, nonce};
        Arrays.sort(parts);
        String content = String.join("", parts);
        try {
            byte[] digest = MessageDigest.getInstance("SHA-1")
                    .digest(content.getBytes(StandardCharsets.UTF_8));
            StringBuilder hex = new StringBuilder();
            for (byte value : digest) {
                hex.append(String.format("%02x", value & 0xff));
            }
            return MessageDigest.isEqual(
                    hex.toString().getBytes(StandardCharsets.US_ASCII),
                    signature.toLowerCase(java.util.Locale.ROOT)
                            .getBytes(StandardCharsets.US_ASCII));
        } catch (NoSuchAlgorithmException ex) {
            throw new IllegalStateException(ex);
        }
    }
}

HTTP 处理器还应限制请求长度,校验失败拒绝响应;成功时按所用明文协议输出 echostr 的原始内容,不再 JSON 序列化。上面只实现签名比较,不包含安全模式解密、消息处理或完整的请求时效与重放控制。公众号接入文档入口

验证地址与接收事件是不同阶段

旧笔记中,验证地址使用 GET,而消息与事件由平台向配置地址推送 POST。GET 成功只能说明当次验证通过,不能证明 POST 路由、网关转发、消息解析和响应都正确。

遇到“没有收到事件”,依次检查:当前账号配置的实际 URL、服务器配置是否启用、POST 是否到达网关和应用、请求体是否按当前模式正确验签及解密。日志只留必要的请求关联信息,避免保存完整用户消息或凭据。

启用开发配置后,菜单要检查来源

旧工程还遇到过启用服务器配置后菜单表现变化。需要分别确认后台配置、接口创建和缓存中的菜单,不能把一次观察写成“启用服务器就必然清空所有菜单”。

48001 应查调用的具体能力

48001 的排查重点是实际接口权限及账号、应用上下文。检查正在用哪个 AppID 的凭证、请求的是哪项接口、平台给当前账号开放了什么能力,不要只反复刷新 access_token。

具体权限可能随账号类型、认证和平台规则变化,本文不沿用 2018 年的权限表作当前保证。二维码事件的后续处理见场景二维码与事件流;字符串多引号则见回调原始响应。