修复:代理转发场景下微信支付 V3 API Authorization 头丢失导致 401#3910
Conversation
Co-authored-by: binarywang <1343140+binarywang@users.noreply.github.com>
🤖 Augment PR SummarySummary: 本 PR 修复了在“通过内网反向代理转发微信支付 V3 API”场景下,请求未携带 Changes:
Technical Notes: 核心策略是“仅对微信官方域名 + 显式/自动注册的代理域名”添加签名头,避免对其他非预期主机发送认证信息。 🤖 Was this summary useful? React with 👍 or 👎 |
| @Override | ||
| protected ClientExecChain decorateProtocolExec(final ClientExecChain requestExecutor) { | ||
| return new SignatureExec(this.credentials, this.validator, requestExecutor); | ||
| return new SignatureExec(this.credentials, this.validator, requestExecutor, this.trustedHosts); |
There was a problem hiding this comment.
这里将可变的 trustedHosts(HashSet)引用直接传入 SignatureExec;如果调用方在 build() 之后继续复用/修改 builder,可能在并发请求时引入非线程安全的读写风险(极端情况下会触发 ConcurrentModificationException 或出现可见性问题)。
Severity: medium
Other Locations
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SignatureExec.java:38
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
| String apiHostUrl = this.getApiHostUrl(); | ||
| if (StringUtils.isNotBlank(apiHostUrl)) { | ||
| try { | ||
| String host = new URI(apiHostUrl).getHost(); |
There was a problem hiding this comment.
new URI(apiHostUrl).getHost() 只有在 apiHostUrl 为“带 scheme 的绝对 URL”时才能解析出 host;如果用户配置成 proxy.company.com:8080/proxy.company.com(无 scheme),这里会得到 null 导致不会自动加入受信任列表,从而 401 问题仍可能复现。建议在文档或参数校验处明确 apiHostUrl/payBaseUrl 的格式要求。
Severity: low
Other Locations
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java:163
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Pull request overview
该 PR 修复微信支付 V3 在“自定义反向代理域名(apiHostUrl/payBaseUrl)”场景下由于 SignatureExec 仅识别微信官方域名而导致 Authorization 头未被添加、进而请求返回 401 的问题;通过引入“受信任主机列表”机制,使代理域名也能走签名/验签链路。
Changes:
- 为
SignatureExec增加trustedHosts支持:当请求 host 命中受信任列表时同样添加Authorization头并执行验签逻辑(保留旧构造函数)。 - 为
WxPayV3HttpClientBuilder增加withTrustedHost(String host),并在构建时将受信任主机传递给SignatureExec。 - 在
WxPayConfig.initApiV3HttpClient()与AutoUpdateCertificatesVerifier.autoUpdateCert()中自动从apiHostUrl/payBaseUrl解析 host 并加入受信任列表;新增对应单测覆盖代理场景。
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| weixin-java-pay/src/test/java/com/github/binarywang/wxpay/v3/SignatureExecTrustedHostTest.java | 新增 trusted host 行为测试,覆盖官方域名/非受信任代理/受信任代理以及旧构造函数兼容性 |
| weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/AutoUpdateCertificatesVerifier.java | 从 payBaseUrl 自动提取代理 host 并加入受信任列表,确保下载证书请求也带 Authorization |
| weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java | 引入受信任主机集合与 withTrustedHost API,并将其传入 SignatureExec |
| weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/SignatureExec.java | 增加 trusted host 判定分支,命中时执行签名逻辑 |
| weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java | 从 apiHostUrl 自动解析代理 host 并注册为受信任主机,避免用户额外手工配置 |
| @Override | ||
| protected ClientExecChain decorateProtocolExec(final ClientExecChain requestExecutor) { | ||
| return new SignatureExec(this.credentials, this.validator, requestExecutor); | ||
| return new SignatureExec(this.credentials, this.validator, requestExecutor, this.trustedHosts); | ||
| } |
There was a problem hiding this comment.
decorateProtocolExec() 里把 Builder 内部的 HashSet trustedHosts 直接传给 SignatureExec,会导致 HttpClient 构建完成后仍然持有一个可变、非线程安全的 Set 引用;如果后续继续复用同一个 Builder 调用 withTrustedHost() 或外部并发修改该 Set,可能触发并发问题或行为漂移。建议在这里传入一个不可变的防御性拷贝(例如复制到新的 Set 后再包成 unmodifiable)。
| public WxPayV3HttpClientBuilder withTrustedHost(String host) { | ||
| if (host != null && !host.isEmpty()) { | ||
| this.trustedHosts.add(host); | ||
| } | ||
| return this; |
There was a problem hiding this comment.
withTrustedHost(String host) 目前仅判断非空/非空串就直接加入集合,但方法注释要求“主机名(不含端口)”。如果调用方误传入 "proxy.company.com:8080" 或包含前后空格的值,会导致 SignatureExec 匹配不到(因为 URI.getHost() 不含端口),功能表现为“配置了 trusted host 但仍不加 Authorization”。建议在加入前对入参做 trim,并剥离端口(或在检测到包含 scheme/端口时按 URI/Host 规则解析出 host 部分)。
| /** | ||
| * 测试:WxPayV3HttpClientBuilder 的 withTrustedHost 方法应该正确设置受信任主机 | ||
| */ | ||
| @Test | ||
| public void testWithTrustedHostBuilderMethod() { | ||
| WxPayV3HttpClientBuilder builder = WxPayV3HttpClientBuilder.create(); | ||
| // 方法应该支持链式调用 | ||
| WxPayV3HttpClientBuilder result = builder.withTrustedHost("proxy.company.com"); | ||
| assertSame(result, builder, "withTrustedHost 应该返回当前 Builder 实例(支持链式调用)"); | ||
| } |
There was a problem hiding this comment.
testWithTrustedHostBuilderMethod 的测试名/注释写的是“应该正确设置受信任主机”,但实际只断言了返回值用于链式调用,没有验证 host 确实影响了最终请求的签名行为。建议要么补充断言(例如 build 后发起对 trusted host 的请求并验证 Authorization 头),要么把测试名和注释改成仅验证链式调用,避免测试意图与覆盖范围不一致。
当用户将
apiHostUrl(或payBaseUrl)配置为内部反向代理地址时,SignatureExec仅对以.mch.weixin.qq.com结尾的主机添加Authorization头,导致转发请求缺少认证信息,微信返回 401。变更
SignatureExec:新增trustedHosts: Set<String>字段;execute()在主机名匹配受信任列表时也执行签名逻辑;旧三参数构造函数保持向后兼容WxPayV3HttpClientBuilder:新增withTrustedHost(String host)链式方法,允许手动注册受信任主机WxPayConfig.initApiV3HttpClient():自动解析apiHostUrl,若为非微信官方主机则注册为受信任主机AutoUpdateCertificatesVerifier.autoUpdateCert():同上,自动处理payBaseUrl中的自定义代理主机配置了自定义
apiHostUrl后,无需额外代码,SDK 自动将代理主机加入受信任列表:如需手动控制,也可通过 builder 显式添加:
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.