Skip to content

🎨 【微信支付】微信支付api-host-url配置反向代理路径前缀时会导致v3的签名异常#3968

Open
shuiyihan12 wants to merge 27 commits intobinarywang:developfrom
shuiyihan12:develop
Open

🎨 【微信支付】微信支付api-host-url配置反向代理路径前缀时会导致v3的签名异常#3968
shuiyihan12 wants to merge 27 commits intobinarywang:developfrom
shuiyihan12:develop

Conversation

@shuiyihan12
Copy link
Copy Markdown
Contributor

简要描述

微信支付api-host-url配置反向代理路径前缀时会导致v3的签名异常

模块版本情况

  • WxJava 模块名:
    • weixin-java-pay
    • wx-java-pay-multi-spring-boot-starter
    • wx-java-pay-solon-plugin
    • wx-java-pay-spring-boot-starter
  • WxJava 版本号: 4.8.2.B

详细描述

当前支持的反向代理配置:

  wx:
    pay:
      api-host: http://127.0.0.1:3338/api-weixin   # v3 支付签名和验签时依赖请求 URI进行签名计算,此时/api-weixin会导致签名异常

异常定位:
App(WxJava) --签名路径:/api-weixin/v3/...-->Proxy(127.0.0.1:3338) --转发/重写-->
WeChatPay(校验路径通常是 /v3/...)=> 路径不一致 => SIGN_ERROR

之后改造:

  wx:
    pay:
      api-host: http://127.0.0.1:3338
      api-host-url: /api-weixin

当前调整后的代码已在生产中应用

m1ngyuan and others added 27 commits July 22, 2024 14:01
@shuiyihan12 shuiyihan12 changed the title 🎨 【微信支付】支持 apiHostUrlPath 代理前缀并修复 V3 签名路径在配置apiHostUrl字段有前缀时验签失败的问题 🎨 【微信支付】微信支付api-host-url配置反向代理路径前缀时会导致v3的签名异常 Apr 22, 2026
@augmentcode
Copy link
Copy Markdown

augmentcode Bot commented Apr 22, 2026

🤖 Augment PR Summary

Summary: This PR improves WeChat Pay API reverse-proxy support by separating the proxy host and an optional path-prefix, and updating V3 signing/verification to account for that prefix.

Changes:

  • Introduces apiHostUrlPath (proxy entry path prefix) in Pay config and starter property models (Spring Boot multi/single + Solon).
  • Normalizes apiHostUrl (trim + trailing-slash removal) and normalizes apiHostUrlPath (leading slash + trailing-slash removal).
  • Adds getApiHostWithPathPrefix() and routes all Pay base-URL construction through host+prefix.
  • Enhances V3 client signing by allowing a configurable “sign URI strip prefix” to remove proxy prefixes from the canonical request path.
  • Updates certificate verifier creation to derive and pass a strip-prefix when the configured base URL contains a path component.
  • Updates README examples and adds tests verifying host/path normalization and property binding.

🤖 Was this summary useful? React with 👍 or 👎

Copy link
Copy Markdown

@augmentcode augmentcode Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review completed. 2 suggestions posted.

Fix All in Augment

Comment augment review to trigger a new review at any time.

}

WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
.withSignUriStripPrefix(this.getApiHostUrlPath())
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weixin-java-pay/.../WxPayConfig.java:435 If a user keeps the legacy style apiHostUrl containing a path prefix (e.g., http://host/api-weixin) but leaves apiHostUrlPath empty, withSignUriStripPrefix stays blank and V3 requests may still sign "/api-weixin/...", so signatures can still fail behind rewrite proxies.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

if (rawPath == null || rawPath.isEmpty() || signUriStripPrefix == null) {
return rawPath;
}
if (!rawPath.startsWith(signUriStripPrefix)) {
Copy link
Copy Markdown

@augmentcode augmentcode Bot Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weixin-java-pay/.../WxPayCredentials.java:123 stripPathPrefix uses rawPath.startsWith(signUriStripPrefix), which can strip unintended paths when the prefix is a partial segment (e.g., prefix /api also matches /api2/...), leading to incorrect canonical URLs and signature failures.

Severity: medium

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

该 PR 针对微信支付在“反向代理带路径前缀”场景下,V3 签名/验签依赖请求 URI Path 导致的签名不一致问题,引入“host 与路径前缀分离配置”,并在签名串构造时对代理前缀进行剥离,从而避免 SIGN_ERROR

Changes:

  • 新增 apiHostUrlPath(代理入口路径前缀)配置,并提供 getApiHostWithPathPrefix() 作为统一的请求基础地址拼接结果。
  • V3 签名串构造时支持剥离代理 path 前缀(signUriStripPrefix),并在 V3 HttpClient/证书更新校验链路中贯通该配置。
  • Spring Boot Starter / Multi Starter / Solon 插件补齐配置项映射、README 示例与相关测试。

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
weixin-java-pay/src/test/java/com/github/binarywang/wxpay/config/WxPayConfigTest.java 增加 apiHostUrl/apiHostUrlPath 规范化与拼接的测试用例
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/auth/WxPayCredentials.java V3 签名 message 构造时增加“Path 前缀剥离”能力
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/v3/WxPayV3HttpClientBuilder.java Builder 支持注入 signUriStripPrefix 并同步到 Credentials
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/BaseWxPayServiceImpl.java getPayBaseUrl() 改为返回 host+pathPrefix,覆盖 v2/v3 URL 生成
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java 新增 apiHostUrlPath、规范化 getter、以及 getApiHostWithPathPrefix();初始化 V3 client 时透传剥离前缀
weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/VerifierBuilder.java payBaseUrl 解析 path 作为签名前缀剥离参数,保证证书下载签名一致
spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/.../WxPayProperties.java Starter 配置项新增 apiHostUrlPath
spring-boot-starters/wx-java-pay-spring-boot-starter/src/main/java/.../WxPayAutoConfiguration.java apiHostUrlPath 映射到 WxPayConfig
spring-boot-starters/wx-java-pay-spring-boot-starter/README.md 文档增加 apiHostUrl/apiHostUrlPath 示例
spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/test/java/.../WxPayMultiServicesTest.java 多配置场景补充 apiHostUrlPath 绑定断言
spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/.../WxPayMultiServicesImpl.java 多配置构建 WxPayConfig 时注入 apiHostUrlPath
spring-boot-starters/wx-java-pay-multi-spring-boot-starter/src/main/java/.../WxPaySingleProperties.java 单配置属性新增 apiHostUrlPath
spring-boot-starters/wx-java-pay-multi-spring-boot-starter/README.md 多配置文档补充 apiHostUrlPath 说明
solon-plugins/wx-java-pay-solon-plugin/src/main/java/.../WxPayProperties.java Solon 插件属性新增 apiHostUrlPath
solon-plugins/wx-java-pay-solon-plugin/src/main/java/.../WxPayAutoConfiguration.java Solon 自动配置注入 apiHostUrlPath
solon-plugins/wx-java-pay-solon-plugin/README.md Solon 文档增加 apiHostUrl/apiHostUrlPath 示例

Comment on lines +123 to +126
if (!rawPath.startsWith(signUriStripPrefix)) {
return rawPath;
}
String stripped = rawPath.substring(signUriStripPrefix.length());
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stripPathPrefix 仅用 rawPath.startsWith(signUriStripPrefix) 判断是否需要剥离前缀,可能发生“部分前缀误匹配”:例如配置前缀为 "/api" 时,请求路径 "/api-weixin/v3/..." 也会被误剥离,导致签名串的 Path 变成"/-weixin/v3/..." 从而签名必然异常。建议改为仅在 rawPath 等于前缀或以 前缀 + "/" 开头时才剥离,避免跨 segment 的误匹配。

Suggested change
if (!rawPath.startsWith(signUriStripPrefix)) {
return rawPath;
}
String stripped = rawPath.substring(signUriStripPrefix.length());
String normalizedPrefix = signUriStripPrefix;
if (normalizedPrefix.length() > 1 && normalizedPrefix.endsWith("/")) {
normalizedPrefix = normalizedPrefix.substring(0, normalizedPrefix.length() - 1);
}
boolean exactMatch = rawPath.equals(normalizedPrefix);
boolean segmentMatch = rawPath.startsWith(normalizedPrefix + "/");
if (!exactMatch && !segmentMatch) {
return rawPath;
}
String stripped = rawPath.substring(normalizedPrefix.length());

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +125
if (rawPath == null || rawPath.isEmpty() || signUriStripPrefix == null) {
return rawPath;
}
if (!rawPath.startsWith(signUriStripPrefix)) {
return rawPath;
}
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的签名 Path 前缀剥离逻辑目前缺少单元测试覆盖。建议至少补充边界 case:前缀等于整个 path、前缀后必须是/才剥离、以及不会误匹配到更长的同前缀路径(如"/api"不应匹配"/api-weixin"),以避免签名回归。

Suggested change
if (rawPath == null || rawPath.isEmpty() || signUriStripPrefix == null) {
return rawPath;
}
if (!rawPath.startsWith(signUriStripPrefix)) {
return rawPath;
}
if (rawPath == null || rawPath.isEmpty() || signUriStripPrefix == null || signUriStripPrefix.isEmpty()) {
return rawPath;
}
if (!rawPath.startsWith(signUriStripPrefix)) {
return rawPath;
}
if (rawPath.length() > signUriStripPrefix.length()
&& rawPath.charAt(signUriStripPrefix.length()) != '/') {
return rawPath;
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants