代码 支付接口日志脱敏:别让 APIv3 密钥、账单下载签名和回调明文进日志

2026-10-10 09:01:48

支付接口日志脱敏:别让 APIv3 密钥、账单下载签名和回调明文进日志

排查支付问题时最顺手的做法是把请求和响应都打出来。这些输出最终会落到日志系统里,而日志系统通常全员可读、留存 30~180 天,还会被同步到 ES / 云日志 / 工单截图。一次 debug 打开,敏感字段就扩散了。

下面按「哪几类字段最容易漏 → 具体从哪漏出去 → 怎么改」走一遍。大部分是工程判断,个别地方标注为未实测。识别方法本身可复现:拿你们的日志检索几条支付相关关键词,看命不命得中。

一、三类最容易漏的敏感字段

  1. 密钥与私钥。微信支付 APIv3 密钥(32 位)、商户私钥 apiclient_key.pem 的内容、平台证书/公钥、Stripe 的 sk_live_/rk_live_、支付宝的应用私钥字符串。这些本来只在初始化时读一次,但 SDK 抛异常时经常把整个配置对象序列化进堆栈。
  2. 回调/请求报文里的密文与明文。微信支付回调的 resource.ciphertext,以及你解密后的明文(含 openid、金额、附言);请求体里的幂等键。密文本身还好,明文才是真正泄漏用户信息的那个。
  3. 带签名的临时 URL 与 Authorization 头。最典型的是账单 download_url——5 分钟有效、不做身份校验就能下载全量交易流水;还有 Authorization: WECHATPAY2-SHA256-RSA2048 ... 这种带签名的头,短时内可复用。

二、具体从哪漏出去

  • 网关 access log:nginx 默认会记 $request_uri,账单下载的 query 里带着 download_url 的全套签名参数,一进日志就等于把「可下载全量流水」的链接抄送给了所有能看日志的人。
  • HTTP 客户端 debug 开关:Go 里 httputil.DumpRequest/DumpResponse、Python requests 打开 urllib3 debug、PHP Guzzle 'debug' => true,都会把 Authorization 和 body 原样打出来。这类开关上线后忘了关是常见事故。
  • 异常处理:catch 之后 log 整个 request 对象;或者 SDK 异常 message 自带签名串(排查 401/SIGN_ERROR 时最容易顺手把签名粘贴进日志或工单)。
  • 前端:JSAPI / 小程序的下单参数 paySign、signature 被 console.log 到生产控制台,或被前端监控 SDK 一起上报。

三、改法:用白名单,不用黑名单

黑名单(列出「不要打哪些」)永远会漏。改成只打明确需要的字段。

微信支付排障真正需要的其实就几个:out_trade_no、transaction_id、trade_state、金额(分)、时间、错误码。其余一律不打。

Go + slog,用 ReplaceAttr 兜底,或者干脆只构造需要的字段:

h := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
    ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
        switch a.Key {
        case "authorization", "private_key", "api_v3_key", "ciphertext", "signature":
            return slog.String(a.Key, "[REDACTED]")
        }
        return a
    },
})

PHP Monolog 加一个 Processor,拦掉整段 $_POST/header:

$logger->pushProcessor(function ($record) {
    foreach (['authorization', 'sign', 'private_key', 'ciphertext'] as $k) {
        if (isset($record['context'][$k])) {
            $record['context'][$k] = '[REDACTED]';
        }
    }
    unset($record['context']['raw_body']); // 不要 log 整个请求体
    return $record;
});

nginx 侧,账单下载这类接口走单独 location,并关掉或裁剪 query 记录:

log_format pay_clean '$remote_addr - $status $request_method $uri'; # 注意:用 $uri,不是 $request_uri(不含 query)
location /pay/bill/ {
    access_log /var/log/nginx/pay_access.log pay_clean;
    proxy_pass http://backend;
}

$uri 不含查询串,$request_uri 含。差异就在这一个变量上。

排障确实要看签名串时,别整段打:只留前后各 4 位(WECHATPAY2-…****),需要完整比对时用离线工具,不要进日志。

四、上线前花十分钟能做的检查

  • 在日志系统里搜 Authorization、api_v3、ciphertext、sk_live、apiclient_key、download_url,看有没有命中。
  • 确认所有 HTTP 客户端的 debug 开关在测试环境为真、生产环境为假(用配置项控制,不要靠人记得关)。
  • 确认网关对账单/回执类 URL 不记完整 query。
  • 确认异常上报(Sentry 之类)对 request body / headers 做了脱敏,很多 SDK 默认是「带上请求体」。
  • 前端生产构建关掉相关 console.log,并检查监控 SDK 有没有把下单参数一起上报。

五、不适用 / 未实测

  • 上面的 ReplaceAttr、Monolog Processor、nginx log_format 片段是常见写法,未在本机跑通具体版本,落地时按你项目的日志框架调整。
  • 具体 SDK 是否默认打请求体、异常上报平台默认带哪些字段,各版本不同,需自行验证。
  • 「日志留存 30~180 天、全员可读」是常见配置,不是规定;真正要紧的是先确认你们自己的日志谁能看、留多久——这决定了泄漏面有多大。

推荐文章

程序员茄子在线接单