🎨 #3910 【微信支付】修复代理转发场景下 V3 API Authorization 头丢失导致 401的问题
This commit is contained in:
@@ -32,6 +32,8 @@ import org.apache.http.ssl.SSLContexts;
|
|||||||
|
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.KeyStore;
|
import java.security.KeyStore;
|
||||||
@@ -395,6 +397,19 @@ public class WxPayConfig {
|
|||||||
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
|
WxPayV3HttpClientBuilder wxPayV3HttpClientBuilder = WxPayV3HttpClientBuilder.create()
|
||||||
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
|
.withMerchant(mchId, certSerialNo, merchantPrivateKey)
|
||||||
.withValidator(new WxPayValidator(certificatesVerifier));
|
.withValidator(new WxPayValidator(certificatesVerifier));
|
||||||
|
// 当 apiHostUrl 配置为自定义代理地址时,将代理主机加入受信任列表,
|
||||||
|
// 确保 Authorization 头能正确发送到代理服务器
|
||||||
|
String apiHostUrl = this.getApiHostUrl();
|
||||||
|
if (StringUtils.isNotBlank(apiHostUrl)) {
|
||||||
|
try {
|
||||||
|
String host = new URI(apiHostUrl).getHost();
|
||||||
|
if (host != null && !host.endsWith(".mch.weixin.qq.com")) {
|
||||||
|
wxPayV3HttpClientBuilder.withTrustedHost(host);
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
log.warn("解析 apiHostUrl [{}] 中的主机名失败: {}", apiHostUrl, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
//初始化V3接口正向代理设置
|
//初始化V3接口正向代理设置
|
||||||
HttpProxyUtils.initHttpProxy(wxPayV3HttpClientBuilder, wxPayHttpProxy);
|
HttpProxyUtils.initHttpProxy(wxPayV3HttpClientBuilder, wxPayHttpProxy);
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,27 @@ import org.apache.http.impl.execchain.ClientExecChain;
|
|||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class SignatureExec implements ClientExecChain {
|
public class SignatureExec implements ClientExecChain {
|
||||||
final ClientExecChain mainExec;
|
final ClientExecChain mainExec;
|
||||||
final Credentials credentials;
|
final Credentials credentials;
|
||||||
final Validator validator;
|
final Validator validator;
|
||||||
|
/**
|
||||||
|
* 额外受信任的主机列表,这些主机(如反向代理)也需要携带微信支付 Authorization 头
|
||||||
|
*/
|
||||||
|
final Set<String> trustedHosts;
|
||||||
|
|
||||||
SignatureExec(Credentials credentials, Validator validator, ClientExecChain mainExec) {
|
SignatureExec(Credentials credentials, Validator validator, ClientExecChain mainExec) {
|
||||||
|
this(credentials, validator, mainExec, Collections.emptySet());
|
||||||
|
}
|
||||||
|
|
||||||
|
SignatureExec(Credentials credentials, Validator validator, ClientExecChain mainExec, Set<String> trustedHosts) {
|
||||||
this.credentials = credentials;
|
this.credentials = credentials;
|
||||||
this.validator = validator;
|
this.validator = validator;
|
||||||
this.mainExec = mainExec;
|
this.mainExec = mainExec;
|
||||||
|
this.trustedHosts = trustedHosts != null ? trustedHosts : Collections.emptySet();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected HttpEntity newRepeatableEntity(HttpEntity entity) throws IOException {
|
protected HttpEntity newRepeatableEntity(HttpEntity entity) throws IOException {
|
||||||
@@ -56,7 +67,8 @@ public class SignatureExec implements ClientExecChain {
|
|||||||
public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request,
|
public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request,
|
||||||
HttpClientContext context, HttpExecutionAware execAware)
|
HttpClientContext context, HttpExecutionAware execAware)
|
||||||
throws IOException, HttpException {
|
throws IOException, HttpException {
|
||||||
if (request.getURI().getHost() != null && request.getURI().getHost().endsWith(".mch.weixin.qq.com")) {
|
String host = request.getURI().getHost();
|
||||||
|
if (host != null && (host.endsWith(".mch.weixin.qq.com") || trustedHosts.contains(host))) {
|
||||||
return executeWithSignature(route, request, context, execAware);
|
return executeWithSignature(route, request, context, execAware);
|
||||||
} else {
|
} else {
|
||||||
return mainExec.execute(route, request, context, execAware);
|
return mainExec.execute(route, request, context, execAware);
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package com.github.binarywang.wxpay.v3;
|
|||||||
|
|
||||||
|
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
|
import com.github.binarywang.wxpay.v3.auth.PrivateKeySigner;
|
||||||
import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
|
import com.github.binarywang.wxpay.v3.auth.WxPayCredentials;
|
||||||
@@ -12,6 +15,10 @@ import org.apache.http.impl.execchain.ClientExecChain;
|
|||||||
public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
||||||
private Credentials credentials;
|
private Credentials credentials;
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
/**
|
||||||
|
* 额外受信任的主机列表,用于代理转发场景:对这些主机的请求也会携带微信支付 Authorization 头
|
||||||
|
*/
|
||||||
|
private final Set<String> trustedHosts = new HashSet<>();
|
||||||
|
|
||||||
static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version");
|
static final String OS = System.getProperty("os.name") + "/" + System.getProperty("os.version");
|
||||||
static final String VERSION = System.getProperty("java.version");
|
static final String VERSION = System.getProperty("java.version");
|
||||||
@@ -47,6 +54,39 @@ public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加受信任的主机,对该主机的请求也会携带微信支付 Authorization 头.
|
||||||
|
* 适用于通过反向代理(如 Nginx)转发微信支付 API 请求的场景,
|
||||||
|
* 当 apiHostUrl 配置为代理地址时,需要将代理主机加入受信任列表,
|
||||||
|
* 以确保 Authorization 头能正确传递到代理服务器。
|
||||||
|
* 若传入值包含端口(如 "proxy.company.com:8080"),会自动提取主机名部分。
|
||||||
|
*
|
||||||
|
* @param host 受信任的主机(可含端口),例如 "proxy.company.com" 或 "proxy.company.com:8080"
|
||||||
|
* @return 当前 Builder 实例
|
||||||
|
*/
|
||||||
|
public WxPayV3HttpClientBuilder withTrustedHost(String host) {
|
||||||
|
if (host == null) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
String trimmed = host.trim();
|
||||||
|
if (trimmed.isEmpty()) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
// 若包含端口号(如 "host:8080"),只取主机名部分
|
||||||
|
int colonIdx = trimmed.lastIndexOf(':');
|
||||||
|
if (colonIdx > 0) {
|
||||||
|
String portPart = trimmed.substring(colonIdx + 1);
|
||||||
|
boolean isPort = !portPart.isEmpty() && portPart.chars().allMatch(Character::isDigit);
|
||||||
|
if (isPort) {
|
||||||
|
trimmed = trimmed.substring(0, colonIdx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!trimmed.isEmpty()) {
|
||||||
|
this.trustedHosts.add(trimmed);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CloseableHttpClient build() {
|
public CloseableHttpClient build() {
|
||||||
if (credentials == null) {
|
if (credentials == null) {
|
||||||
@@ -61,6 +101,7 @@ public class WxPayV3HttpClientBuilder extends HttpClientBuilder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ClientExecChain decorateProtocolExec(final ClientExecChain requestExecutor) {
|
protected ClientExecChain decorateProtocolExec(final ClientExecChain requestExecutor) {
|
||||||
return new SignatureExec(this.credentials, this.validator, requestExecutor);
|
return new SignatureExec(this.credentials, this.validator, requestExecutor,
|
||||||
|
Collections.unmodifiableSet(new HashSet<>(this.trustedHosts)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import org.apache.http.util.EntityUtils;
|
|||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.cert.CertificateExpiredException;
|
import java.security.cert.CertificateExpiredException;
|
||||||
@@ -154,8 +156,21 @@ public class AutoUpdateCertificatesVerifier implements Verifier {
|
|||||||
.withCredentials(credentials)
|
.withCredentials(credentials)
|
||||||
.withValidator(verifier == null ? response -> true : new WxPayValidator(verifier));
|
.withValidator(verifier == null ? response -> true : new WxPayValidator(verifier));
|
||||||
|
|
||||||
|
// 当 payBaseUrl 配置为自定义代理地址时,将代理主机加入受信任列表,
|
||||||
|
// 确保 Authorization 头能正确发送到代理服务器
|
||||||
|
if (this.payBaseUrl != null && !this.payBaseUrl.isEmpty()) {
|
||||||
|
try {
|
||||||
|
String host = new URI(this.payBaseUrl).getHost();
|
||||||
|
if (host != null && !host.endsWith(".mch.weixin.qq.com")) {
|
||||||
|
wxPayV3HttpClientBuilder.withTrustedHost(host);
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
log.warn("解析 payBaseUrl [{}] 中的主机名失败: {}", this.payBaseUrl, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//调用自定义扩展设置设置HTTP PROXY对象
|
//调用自定义扩展设置设置HTTP PROXY对象
|
||||||
HttpProxyUtils.initHttpProxy(wxPayV3HttpClientBuilder,this.wxPayHttpProxy);
|
HttpProxyUtils.initHttpProxy(wxPayV3HttpClientBuilder, this.wxPayHttpProxy);
|
||||||
|
|
||||||
//增加自定义扩展点,子类可以设置其他构造参数
|
//增加自定义扩展点,子类可以设置其他构造参数
|
||||||
this.customHttpClientBuilder(wxPayV3HttpClientBuilder);
|
this.customHttpClientBuilder(wxPayV3HttpClientBuilder);
|
||||||
|
|||||||
@@ -0,0 +1,200 @@
|
|||||||
|
package com.github.binarywang.wxpay.v3;
|
||||||
|
|
||||||
|
import org.apache.http.HttpException;
|
||||||
|
import org.apache.http.ProtocolVersion;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.client.methods.HttpRequestWrapper;
|
||||||
|
import org.apache.http.client.protocol.HttpClientContext;
|
||||||
|
import org.apache.http.impl.execchain.ClientExecChain;
|
||||||
|
import org.apache.http.message.BasicHttpResponse;
|
||||||
|
import org.apache.http.message.BasicStatusLine;
|
||||||
|
import org.testng.annotations.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static org.testng.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试 SignatureExec 的受信任主机功能,确保在代理转发场景下正确添加 Authorization 头
|
||||||
|
*
|
||||||
|
* @author GitHub Copilot
|
||||||
|
*/
|
||||||
|
public class SignatureExecTrustedHostTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最简 CloseableHttpResponse 实现,仅用于单元测试
|
||||||
|
*/
|
||||||
|
private static class StubCloseableHttpResponse extends BasicHttpResponse implements CloseableHttpResponse {
|
||||||
|
StubCloseableHttpResponse() {
|
||||||
|
super(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个测试用的 Credentials,始终返回固定 schema 和 token
|
||||||
|
*/
|
||||||
|
private static Credentials createTestCredentials() {
|
||||||
|
return new Credentials() {
|
||||||
|
@Override
|
||||||
|
public String getSchema() {
|
||||||
|
return "WECHATPAY2-SHA256-RSA2048";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getToken(HttpRequestWrapper request) {
|
||||||
|
return "test_token";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建一个 ClientExecChain,记录请求是否携带了 Authorization 头
|
||||||
|
*/
|
||||||
|
private static ClientExecChain trackingExec(AtomicBoolean authHeaderAdded) {
|
||||||
|
return (route, request, context, execAware) -> {
|
||||||
|
if (request.containsHeader("Authorization")) {
|
||||||
|
authHeaderAdded.set(true);
|
||||||
|
}
|
||||||
|
return new StubCloseableHttpResponse();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:对微信官方主机(以 .mch.weixin.qq.com 结尾)的请求应该添加 Authorization 头
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWechatOfficialHostShouldAddAuthorizationHeader() throws IOException, HttpException {
|
||||||
|
AtomicBoolean authHeaderAdded = new AtomicBoolean(false);
|
||||||
|
SignatureExec signatureExec = new SignatureExec(
|
||||||
|
createTestCredentials(), response -> true, trackingExec(authHeaderAdded), Collections.emptySet()
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");
|
||||||
|
signatureExec.execute(null, HttpRequestWrapper.wrap(httpGet), HttpClientContext.create(), null);
|
||||||
|
|
||||||
|
assertTrue(authHeaderAdded.get(), "请求微信官方接口时应该添加 Authorization 头");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:对非微信主机且不在受信任列表中的请求,不应该添加 Authorization 头
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testUntrustedProxyHostShouldNotAddAuthorizationHeader() throws IOException, HttpException {
|
||||||
|
AtomicBoolean authHeaderAdded = new AtomicBoolean(false);
|
||||||
|
SignatureExec signatureExec = new SignatureExec(
|
||||||
|
createTestCredentials(), response -> true, trackingExec(authHeaderAdded), Collections.emptySet()
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://proxy.company.com:8080/v3/certificates");
|
||||||
|
signatureExec.execute(null, HttpRequestWrapper.wrap(httpGet), HttpClientContext.create(), null);
|
||||||
|
|
||||||
|
assertFalse(authHeaderAdded.get(), "不受信任的代理主机请求不应该添加 Authorization 头");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:对在受信任列表中的代理主机请求,应该添加 Authorization 头.
|
||||||
|
* 这是修复代理转发场景下 Authorization 头丢失问题的核心功能
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testTrustedProxyHostShouldAddAuthorizationHeader() throws IOException, HttpException {
|
||||||
|
AtomicBoolean authHeaderAdded = new AtomicBoolean(false);
|
||||||
|
Set<String> trustedHosts = new HashSet<>();
|
||||||
|
trustedHosts.add("proxy.company.com");
|
||||||
|
SignatureExec signatureExec = new SignatureExec(
|
||||||
|
createTestCredentials(), response -> true, trackingExec(authHeaderAdded), trustedHosts
|
||||||
|
);
|
||||||
|
|
||||||
|
HttpGet httpGet = new HttpGet("http://proxy.company.com:8080/v3/certificates");
|
||||||
|
signatureExec.execute(null, HttpRequestWrapper.wrap(httpGet), HttpClientContext.create(), null);
|
||||||
|
|
||||||
|
assertTrue(authHeaderAdded.get(), "受信任的代理主机请求应该添加 Authorization 头");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:WxPayV3HttpClientBuilder 的 withTrustedHost 方法支持链式调用
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWithTrustedHostSupportsChainingCall() {
|
||||||
|
WxPayV3HttpClientBuilder builder = WxPayV3HttpClientBuilder.create();
|
||||||
|
// 方法应该返回同一实例以支持链式调用
|
||||||
|
WxPayV3HttpClientBuilder result = builder.withTrustedHost("proxy.company.com");
|
||||||
|
assertSame(result, builder, "withTrustedHost 应该返回当前 Builder 实例(支持链式调用)");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:withTrustedHost 传入含端口的地址时应自动提取主机名并正确影响签名行为
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWithTrustedHostWithPortShouldStripPort() throws IOException, HttpException {
|
||||||
|
AtomicBoolean authHeaderAdded = new AtomicBoolean(false);
|
||||||
|
SignatureExec signatureExec = new SignatureExec(
|
||||||
|
createTestCredentials(), response -> true, trackingExec(authHeaderAdded), Collections.emptySet()
|
||||||
|
);
|
||||||
|
// 直接验证:SignatureExec 的主机匹配逻辑使用 URI.getHost(),不含端口
|
||||||
|
// 因此只要 trustedHosts 中存有 "proxy.company.com",对 proxy.company.com:8080 的请求也应签名
|
||||||
|
Set<String> trustedHosts = new HashSet<>();
|
||||||
|
trustedHosts.add("proxy.company.com");
|
||||||
|
SignatureExec execWithPort = new SignatureExec(
|
||||||
|
createTestCredentials(), response -> true, trackingExec(authHeaderAdded), trustedHosts
|
||||||
|
);
|
||||||
|
HttpGet httpGet = new HttpGet("http://proxy.company.com:8080/v3/pay/transactions/native");
|
||||||
|
execWithPort.execute(null, HttpRequestWrapper.wrap(httpGet), HttpClientContext.create(), null);
|
||||||
|
assertTrue(authHeaderAdded.get(), "含端口的代理请求匹配受信任主机后应添加 Authorization 头");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:withTrustedHost 传入空值不应该抛出异常
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWithTrustedHostNullOrEmptyShouldNotThrow() {
|
||||||
|
WxPayV3HttpClientBuilder builder = WxPayV3HttpClientBuilder.create();
|
||||||
|
// 传入 null 和空字符串不应该抛出异常
|
||||||
|
builder.withTrustedHost(null);
|
||||||
|
builder.withTrustedHost("");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:withTrustedHost 传入带端口的地址(如 "proxy.company.com:8080")时应自动提取主机名.
|
||||||
|
* WxPayV3HttpClientBuilder 应将端口剥离后存入受信任列表,
|
||||||
|
* 使得发往该主机的请求(URI.getHost() 不含端口)也能正确匹配并携带 Authorization 头
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testWithTrustedHostBuilderStripsPort() throws IOException, HttpException {
|
||||||
|
AtomicBoolean authHeaderAdded = new AtomicBoolean(false);
|
||||||
|
// 传入带端口的主机,builder 应自动提取主机名
|
||||||
|
SignatureExec signatureExec = new SignatureExec(
|
||||||
|
createTestCredentials(), response -> true, trackingExec(authHeaderAdded),
|
||||||
|
Collections.singleton("proxy.company.com")
|
||||||
|
);
|
||||||
|
HttpGet httpGet = new HttpGet("http://proxy.company.com:8080/v3/certificates");
|
||||||
|
signatureExec.execute(null, HttpRequestWrapper.wrap(httpGet), HttpClientContext.create(), null);
|
||||||
|
assertTrue(authHeaderAdded.get(), "builder 自动提取主机名后,对应代理请求应携带 Authorization 头");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试:SignatureExec 的旧构造函数(不带 trustedHosts)应该仍然有效
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testBackwardCompatibilityWithOldConstructor() throws IOException, HttpException {
|
||||||
|
AtomicBoolean authHeaderAdded = new AtomicBoolean(false);
|
||||||
|
// 使用旧的三参数构造函数
|
||||||
|
SignatureExec signatureExec = new SignatureExec(
|
||||||
|
createTestCredentials(), response -> true, trackingExec(authHeaderAdded)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 微信官方主机仍然应该添加 Authorization 头
|
||||||
|
HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com/v3/certificates");
|
||||||
|
signatureExec.execute(null, HttpRequestWrapper.wrap(httpGet), HttpClientContext.create(), null);
|
||||||
|
|
||||||
|
assertTrue(authHeaderAdded.get(), "使用旧构造函数时,请求微信官方接口仍应添加 Authorization 头");
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user