-
-
Notifications
You must be signed in to change notification settings - Fork 9.1k
🎨 【微信支付】微信支付api-host-url配置反向代理路径前缀时会导致v3的签名异常 #3968
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
bbab553
3d8e8e5
26ee4c4
764a8d7
5c1aacc
3413c05
b9c192e
ce6ab6b
c25ae64
c835b1f
50801eb
6bb0f9b
39a2702
6ff8774
d7ecfe3
008c1c7
4549a1b
6e1285c
0fe3994
70915d2
e5399fe
f027505
44cfeb3
0a31386
0195f14
2cb4273
58b1839
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,16 +20,42 @@ public class WxPayCredentials implements Credentials { | |||||||||||||||||||||||||||||||||||
| private static final SecureRandom RANDOM = new SecureRandom(); | ||||||||||||||||||||||||||||||||||||
| protected String merchantId; | ||||||||||||||||||||||||||||||||||||
| protected Signer signer; | ||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||
| * 签名前从 URI Path 中移除的前缀(用于带路径前缀的反向代理场景) | ||||||||||||||||||||||||||||||||||||
| * 例如配置为 "/api-weixin" 时,"/api-weixin/v3/pay/..." 将参与签名为 "/v3/pay/..." | ||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||
| protected String signUriStripPrefix; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| public WxPayCredentials(String merchantId, Signer signer) { | ||||||||||||||||||||||||||||||||||||
| this.merchantId = merchantId; | ||||||||||||||||||||||||||||||||||||
| this.signer = signer; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| public WxPayCredentials(String merchantId, Signer signer, String signUriStripPrefix) { | ||||||||||||||||||||||||||||||||||||
| this.merchantId = merchantId; | ||||||||||||||||||||||||||||||||||||
| this.signer = signer; | ||||||||||||||||||||||||||||||||||||
| this.setSignUriStripPrefix(signUriStripPrefix); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| public String getMerchantId() { | ||||||||||||||||||||||||||||||||||||
| return merchantId; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| public void setSignUriStripPrefix(String signUriStripPrefix) { | ||||||||||||||||||||||||||||||||||||
| if (signUriStripPrefix == null || signUriStripPrefix.trim().isEmpty()) { | ||||||||||||||||||||||||||||||||||||
| this.signUriStripPrefix = null; | ||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| String normalized = signUriStripPrefix.trim(); | ||||||||||||||||||||||||||||||||||||
| if (!normalized.startsWith("/")) { | ||||||||||||||||||||||||||||||||||||
| normalized = "/" + normalized; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| if (normalized.length() > 1 && normalized.endsWith("/")) { | ||||||||||||||||||||||||||||||||||||
| normalized = normalized.substring(0, normalized.length() - 1); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| this.signUriStripPrefix = normalized; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| protected long generateTimestamp() { | ||||||||||||||||||||||||||||||||||||
| return System.currentTimeMillis() / 1000; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
@@ -70,7 +96,7 @@ public final String getToken(HttpRequestWrapper request) throws IOException { | |||||||||||||||||||||||||||||||||||
| protected final String buildMessage(String nonce, long timestamp, HttpRequestWrapper request) | ||||||||||||||||||||||||||||||||||||
| throws IOException { | ||||||||||||||||||||||||||||||||||||
| URI uri = request.getURI(); | ||||||||||||||||||||||||||||||||||||
| String canonicalUrl = uri.getRawPath(); | ||||||||||||||||||||||||||||||||||||
| String canonicalUrl = stripPathPrefix(uri.getRawPath()); | ||||||||||||||||||||||||||||||||||||
| if (uri.getQuery() != null) { | ||||||||||||||||||||||||||||||||||||
| canonicalUrl += "?" + uri.getRawQuery(); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
@@ -90,4 +116,18 @@ protected final String buildMessage(String nonce, long timestamp, HttpRequestWra | |||||||||||||||||||||||||||||||||||
| + body + "\n"; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| private String stripPathPrefix(String rawPath) { | ||||||||||||||||||||||||||||||||||||
| if (rawPath == null || rawPath.isEmpty() || signUriStripPrefix == null) { | ||||||||||||||||||||||||||||||||||||
| return rawPath; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| if (!rawPath.startsWith(signUriStripPrefix)) { | ||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Severity: medium 🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage. |
||||||||||||||||||||||||||||||||||||
| return rawPath; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+120
to
+125
|
||||||||||||||||||||||||||||||||||||
| 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
AI
Apr 22, 2026
There was a problem hiding this comment.
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 的误匹配。
| 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()); |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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:435If a user keeps the legacy styleapiHostUrlcontaining a path prefix (e.g.,http://host/api-weixin) but leavesapiHostUrlPathempty,withSignUriStripPrefixstays blank and V3 requests may still sign"/api-weixin/...", so signatures can still fail behind rewrite proxies.Severity: medium
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.