🎨 #3968【微信支付】修复微信支付api-host-url配置反向代理路径前缀时会导致v3签名异常的问题
This commit is contained in:
@@ -6,6 +6,8 @@ import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
@@ -118,8 +120,19 @@ class VerifierBuilder {
|
||||
String certSerialNo, String mchId, String apiV3Key, PrivateKey merchantPrivateKey,
|
||||
WxPayHttpProxy wxPayHttpProxy, int certAutoUpdateTime, String payBaseUrl
|
||||
) {
|
||||
String signUriStripPrefix = null;
|
||||
if (StringUtils.isNotBlank(payBaseUrl)) {
|
||||
try {
|
||||
String rawPath = new URI(payBaseUrl).getRawPath();
|
||||
if (StringUtils.isNotBlank(rawPath) && !"/".equals(rawPath)) {
|
||||
signUriStripPrefix = rawPath;
|
||||
}
|
||||
} catch (URISyntaxException ignored) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
return new AutoUpdateCertificatesVerifier(
|
||||
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey)),
|
||||
new WxPayCredentials(mchId, new PrivateKeySigner(certSerialNo, merchantPrivateKey), signUriStripPrefix),
|
||||
apiV3Key.getBytes(StandardCharsets.UTF_8), certAutoUpdateTime,
|
||||
payBaseUrl, wxPayHttpProxy);
|
||||
}
|
||||
|
||||
@@ -64,6 +64,12 @@ public class WxPayConfig {
|
||||
*/
|
||||
private String apiHostUrl = DEFAULT_PAY_BASE_URL;
|
||||
|
||||
/**
|
||||
* 微信支付接口请求地址路径前缀(用于网关代理前缀).
|
||||
* 例如:/api-weixin
|
||||
*/
|
||||
private String apiHostUrlPath;
|
||||
|
||||
/**
|
||||
* http请求连接超时时间.
|
||||
*/
|
||||
@@ -285,11 +291,42 @@ public class WxPayConfig {
|
||||
* @return 微信支付接口请求地址域名
|
||||
*/
|
||||
public String getApiHostUrl() {
|
||||
if (StringUtils.isEmpty(this.apiHostUrl)) {
|
||||
String hostUrl = StringUtils.trimToNull(this.apiHostUrl);
|
||||
if (hostUrl == null) {
|
||||
return DEFAULT_PAY_BASE_URL;
|
||||
}
|
||||
if (hostUrl.endsWith("/")) {
|
||||
hostUrl = hostUrl.substring(0, hostUrl.length() - 1);
|
||||
}
|
||||
return hostUrl;
|
||||
}
|
||||
|
||||
return this.apiHostUrl;
|
||||
/**
|
||||
* 返回所设置的微信支付接口路径前缀.
|
||||
*
|
||||
* @return 路径前缀,不配置时为空字符串
|
||||
*/
|
||||
public String getApiHostUrlPath() {
|
||||
String pathPrefix = StringUtils.trimToNull(this.apiHostUrlPath);
|
||||
if (pathPrefix == null || "/".equals(pathPrefix)) {
|
||||
return "";
|
||||
}
|
||||
if (!pathPrefix.startsWith("/")) {
|
||||
pathPrefix = "/" + pathPrefix;
|
||||
}
|
||||
if (pathPrefix.endsWith("/")) {
|
||||
pathPrefix = pathPrefix.substring(0, pathPrefix.length() - 1);
|
||||
}
|
||||
return pathPrefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回用于请求层拼接的基础地址:host + pathPrefix.
|
||||
*
|
||||
* @return 拼接后的基础地址
|
||||
*/
|
||||
public String getApiHostWithPathPrefix() {
|
||||
return this.getApiHostUrl() + this.getApiHostUrlPath();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@@ -391,10 +428,11 @@ public class WxPayConfig {
|
||||
} else {
|
||||
certificatesVerifier = VerifierBuilder.build(
|
||||
this.getCertSerialNo(), this.getMchId(), this.getApiV3Key(), merchantPrivateKey, wxPayHttpProxy,
|
||||
this.getCertAutoUpdateTime(), this.getApiHostUrl(), this.getPublicKeyId(), publicKey);
|
||||
this.getCertAutoUpdateTime(), this.getApiHostWithPathPrefix(), this.getPublicKeyId(), publicKey);
|
||||
}
|
||||
|
||||
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
|
||||
.withSignUriStripPrefix(this.getApiHostUrlPath())
|
||||
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
|
||||
.withValidator(new WxPayValidator(certificatesVerifier));
|
||||
// 当 apiHostUrl 配置为自定义代理地址时,将代理主机加入受信任列表,
|
||||
|
||||
@@ -366,9 +366,9 @@ public abstract class BaseWxPayServiceImpl implements WxPayService {
|
||||
if (StringUtils.isNotBlank(this.getConfig().getApiV3Key())) {
|
||||
throw new WxRuntimeException("微信支付V3 目前不支持沙箱模式!");
|
||||
}
|
||||
return this.getConfig().getApiHostUrl() + "/xdc/apiv2sandbox";
|
||||
return this.getConfig().getApiHostWithPathPrefix() + "/xdc/apiv2sandbox";
|
||||
}
|
||||
return this.getConfig().getApiHostUrl();
|
||||
return this.getConfig().getApiHostWithPathPrefix();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -15,6 +15,10 @@ import org.apache.http.impl.execchain.ClientExecChain;
|
||||
public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
||||
private Credentials credentials;
|
||||
private Validator validator;
|
||||
/**
|
||||
* 签名前从请求 URI Path 中移除的前缀(用于带路径前缀的代理场景)
|
||||
*/
|
||||
private String signUriStripPrefix;
|
||||
/**
|
||||
* 额外受信任的主机列表,用于代理转发场景:对这些主机的请求也会携带微信支付 Authorization 头
|
||||
*/
|
||||
@@ -40,12 +44,30 @@ public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
||||
|
||||
public WxPayV3HttpClientBuilder withMerchant(String merchantId, String serialNo, PrivateKey privateKey) {
|
||||
this.credentials =
|
||||
new WxPayCredentials(merchantId, new PrivateKeySigner(serialNo, privateKey));
|
||||
new WxPayCredentials(merchantId, new PrivateKeySigner(serialNo, privateKey), this.signUriStripPrefix);
|
||||
return this;
|
||||
}
|
||||
|
||||
public WxPayV3HttpClientBuilder withCredentials(Credentials credentials) {
|
||||
this.credentials = credentials;
|
||||
if (this.credentials instanceof WxPayCredentials) {
|
||||
((WxPayCredentials) this.credentials).setSignUriStripPrefix(this.signUriStripPrefix);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置签名前需要移除的 URI Path 前缀.
|
||||
* 例如设置为 "/api-weixin" 时,签名串中的 Path 会从 "/api-weixin/v3/..." 调整为 "/v3/..."。
|
||||
*
|
||||
* @param signUriStripPrefix 需要移除的前缀
|
||||
* @return 当前 Builder 实例
|
||||
*/
|
||||
public WxPayV3HttpClientBuilder withSignUriStripPrefix(String signUriStripPrefix) {
|
||||
this.signUriStripPrefix = signUriStripPrefix;
|
||||
if (this.credentials instanceof WxPayCredentials) {
|
||||
((WxPayCredentials) this.credentials).setSignUriStripPrefix(signUriStripPrefix);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 class WxPayCredentials implements Credentials {
|
||||
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 @@ public class WxPayCredentials implements Credentials {
|
||||
+ body + "\n";
|
||||
}
|
||||
|
||||
private String stripPathPrefix(String rawPath) {
|
||||
if (rawPath == null || rawPath.isEmpty() || signUriStripPrefix == null) {
|
||||
return rawPath;
|
||||
}
|
||||
if (!rawPath.startsWith(signUriStripPrefix)) {
|
||||
return rawPath;
|
||||
}
|
||||
String stripped = rawPath.substring(signUriStripPrefix.length());
|
||||
if (stripped.isEmpty()) {
|
||||
return "/";
|
||||
}
|
||||
return stripped.startsWith("/") ? stripped : "/" + stripped;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.github.binarywang.wxpay.config;
|
||||
|
||||
import org.testng.annotations.Test;
|
||||
|
||||
import static org.testng.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* Created by BinaryWang on 2017/6/18.
|
||||
@@ -38,6 +40,15 @@ public class WxPayConfigTest {
|
||||
payConfig.hashCode();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testApiHostUrlPath() {
|
||||
payConfig.setApiHostUrl("http://10.0.0.1:3128/");
|
||||
payConfig.setApiHostUrlPath("api-weixin/");
|
||||
assertEquals(payConfig.getApiHostUrl(), "http://10.0.0.1:3128");
|
||||
assertEquals(payConfig.getApiHostUrlPath(), "/api-weixin");
|
||||
assertEquals(payConfig.getApiHostWithPathPrefix(), "http://10.0.0.1:3128/api-weixin");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitSSLContext_base64() throws Exception {
|
||||
payConfig.setMchId("123");
|
||||
|
||||
Reference in New Issue
Block a user